mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-13 04:53:01 +00:00
2.0: jupyterlab by default
swaps from default nbclassic and opt-in to lab, to now default to lab and opt-in to nbclassic defaults to jupyterlab *if* lab 3.1 is available, so should still work without configuration if lab is unavailable (or too old)
This commit is contained in:
15
.github/workflows/test.yml
vendored
15
.github/workflows/test.yml
vendored
@@ -38,9 +38,9 @@ jobs:
|
||||
# Tests everything when JupyterHub works against a dedicated mysql or
|
||||
# postgresql server.
|
||||
#
|
||||
# jupyter_server:
|
||||
# nbclassic:
|
||||
# Tests everything when the user instances are started with
|
||||
# jupyter_server instead of notebook.
|
||||
# notebook instead of jupyter_server.
|
||||
#
|
||||
# ssl:
|
||||
# Tests everything using internal SSL connections instead of
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
#
|
||||
# main_dependencies:
|
||||
# Tests everything when the we use the latest available dependencies
|
||||
# from: ipytraitlets.
|
||||
# from: traitlets.
|
||||
#
|
||||
# NOTE: Since only the value of these parameters are presented in the
|
||||
# GitHub UI when the workflow run, we avoid using true/false as
|
||||
@@ -56,6 +56,7 @@ jobs:
|
||||
include:
|
||||
- python: "3.6"
|
||||
oldest_dependencies: oldest_dependencies
|
||||
nbclassic: nbclassic
|
||||
- python: "3.6"
|
||||
subdomain: subdomain
|
||||
- python: "3.7"
|
||||
@@ -65,7 +66,7 @@ jobs:
|
||||
- python: "3.8"
|
||||
db: postgres
|
||||
- python: "3.8"
|
||||
jupyter_server: jupyter_server
|
||||
nbclassic: nbclassic
|
||||
- python: "3.9"
|
||||
main_dependencies: main_dependencies
|
||||
|
||||
@@ -130,9 +131,9 @@ jobs:
|
||||
if [ "${{ matrix.main_dependencies }}" != "" ]; then
|
||||
pip install git+https://github.com/ipython/traitlets#egg=traitlets --force
|
||||
fi
|
||||
if [ "${{ matrix.jupyter_server }}" != "" ]; then
|
||||
pip uninstall notebook --yes
|
||||
pip install jupyter_server
|
||||
if [ "${{ matrix.nbclassic }}" != "" ]; then
|
||||
pip uninstall jupyter_server --yes
|
||||
pip install notebook
|
||||
fi
|
||||
if [ "${{ matrix.db }}" == "mysql" ]; then
|
||||
pip install mysql-connector-python
|
||||
|
@@ -7,8 +7,8 @@ codecov
|
||||
coverage
|
||||
cryptography
|
||||
html5lib # needed for beautifulsoup
|
||||
jupyterlab >=3
|
||||
mock
|
||||
notebook
|
||||
pre-commit
|
||||
pytest>=3.3
|
||||
pytest-asyncio
|
||||
|
@@ -76,13 +76,26 @@ c.InteractiveShellApp.extensions.append("cython")
|
||||
|
||||
### Example: Enable a Jupyter notebook configuration setting for all users
|
||||
|
||||
:::{note}
|
||||
These examples configure the Jupyter ServerApp,
|
||||
which is used by JupyterLab, the default in JupyterHub 2.0.
|
||||
|
||||
If you are using the classing Jupyter Notebook server,
|
||||
the same things should work,
|
||||
with the following substitutions:
|
||||
|
||||
- Where you see `jupyter_server_config`, use `jupyter_notebook_config`
|
||||
- Where you see `NotebookApp`, use `ServerApp`
|
||||
|
||||
:::
|
||||
|
||||
To enable Jupyter notebook's internal idle-shutdown behavior (requires
|
||||
notebook ≥ 5.4), set the following in the `/etc/jupyter/jupyter_notebook_config.py`
|
||||
notebook ≥ 5.4), set the following in the `/etc/jupyter/jupyter_server_config.py`
|
||||
file:
|
||||
|
||||
```python
|
||||
# shutdown the server after no activity for an hour
|
||||
c.NotebookApp.shutdown_no_activity_timeout = 60 * 60
|
||||
c.ServerApp.shutdown_no_activity_timeout = 60 * 60
|
||||
# shutdown kernels after no activity for 20 minutes
|
||||
c.MappingKernelManager.cull_idle_timeout = 20 * 60
|
||||
# check for idle kernels every two minutes
|
||||
@@ -112,8 +125,8 @@ Assuming I have a Python 2 and Python 3 environment that I want to make
|
||||
sure are available, I can install their specs system-wide (in /usr/local) with:
|
||||
|
||||
```bash
|
||||
/path/to/python3 -m IPython kernel install --prefix=/usr/local
|
||||
/path/to/python2 -m IPython kernel install --prefix=/usr/local
|
||||
/path/to/python3 -m ipykernel install --prefix=/usr/local
|
||||
/path/to/python2 -m ipykernel install --prefix=/usr/local
|
||||
```
|
||||
|
||||
## Multi-user hosts vs. Containers
|
||||
@@ -176,12 +189,38 @@ The number of named servers per user can be limited by setting
|
||||
c.JupyterHub.named_server_limit_per_user = 5
|
||||
```
|
||||
|
||||
## Switching to Jupyter Server
|
||||
## Switching back to classic notebook
|
||||
|
||||
[Jupyter Server](https://jupyter-server.readthedocs.io/en/latest/) is a new Tornado Server backend for Jupyter web applications (e.g. JupyterLab 3.0 uses this package as its default backend).
|
||||
By default the single-user server launches JupyterLab,
|
||||
which is based on [Jupyter Server][].
|
||||
This is the default server when running JupyterHub ≥ 2.0.
|
||||
You can switch to using the legacy Jupyter Notebook server by setting the `JUPYTERHUB_SINGLEUSER_APP` environment variable
|
||||
(in the single-user environment) to:
|
||||
|
||||
By default, the single-user notebook server uses the (old) `NotebookApp` from the [notebook](https://github.com/jupyter/notebook) package. You can switch to using Jupyter Server's `ServerApp` backend (this will likely become the default in future releases) by setting the `JUPYTERHUB_SINGLEUSER_APP` environment variable to:
|
||||
```bash
|
||||
export JUPYTERHUB_SINGLEUSER_APP='notebook.notebookapp.NotebookApp'
|
||||
```
|
||||
|
||||
[jupyter server]: https://jupyter-server.readthedocs.io
|
||||
[jupyter notebook]: https://jupyter-notebook.readthedocs.io
|
||||
|
||||
:::{versionchanged} 2.0
|
||||
JupyterLab is now the default singleuser UI, if available,
|
||||
which is based on the [Jupyter Server][],
|
||||
no longer the legacy [Jupyter Notebook][] server.
|
||||
JupyterHub prior to 2.0 launched the legacy notebook server (`jupyter notebook`),
|
||||
and Jupyter server could be selected by specifying
|
||||
|
||||
```python
|
||||
# jupyterhub_config.py
|
||||
c.Spawner.cmd = ["jupyter-labhub"]
|
||||
```
|
||||
|
||||
or for an otherwise customized Jupyter Server app,
|
||||
set the environment variable:
|
||||
|
||||
```bash
|
||||
export JUPYTERHUB_SINGLEUSER_APP='jupyter_server.serverapp.ServerApp'
|
||||
```
|
||||
|
||||
:::
|
||||
|
@@ -1,7 +1,12 @@
|
||||
"""Make a single-user app based on the environment:
|
||||
|
||||
- $JUPYTERHUB_SINGLEUSER_APP, the base Application class, to be wrapped in JupyterHub authentication.
|
||||
default: notebook.notebookapp.NotebookApp
|
||||
default: jupyter_server.serverapp.ServerApp
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
|
||||
Default app changed to launch `jupyter labhub`.
|
||||
Use JUPYTERHUB_SINGLEUSER_APP=notebook.notebookapp.NotebookApp for the legacy 'classic' notebook server.
|
||||
"""
|
||||
import os
|
||||
|
||||
@@ -9,12 +14,55 @@ from traitlets import import_item
|
||||
|
||||
from .mixins import make_singleuser_app
|
||||
|
||||
JUPYTERHUB_SINGLEUSER_APP = (
|
||||
os.environ.get("JUPYTERHUB_SINGLEUSER_APP") or "notebook.notebookapp.NotebookApp"
|
||||
)
|
||||
JUPYTERHUB_SINGLEUSER_APP = os.environ.get("JUPYTERHUB_SINGLEUSER_APP")
|
||||
|
||||
|
||||
if JUPYTERHUB_SINGLEUSER_APP:
|
||||
App = import_item(JUPYTERHUB_SINGLEUSER_APP)
|
||||
else:
|
||||
App = None
|
||||
_import_error = None
|
||||
for JUPYTERHUB_SINGLEUSER_APP in (
|
||||
"jupyter_server.serverapp.ServerApp",
|
||||
"notebook.notebookapp.NotebookApp",
|
||||
):
|
||||
try:
|
||||
App = import_item(JUPYTERHUB_SINGLEUSER_APP)
|
||||
except ImportError as e:
|
||||
continue
|
||||
if _import_error is None:
|
||||
_import_error = e
|
||||
else:
|
||||
break
|
||||
if App is None:
|
||||
raise _import_error
|
||||
|
||||
|
||||
SingleUserNotebookApp = make_singleuser_app(App)
|
||||
|
||||
main = SingleUserNotebookApp.launch_instance
|
||||
|
||||
def main():
|
||||
"""Launch a jupyterhub single-user server"""
|
||||
if not os.environ.get("JUPYTERHUB_SINGLEUSER_APP"):
|
||||
# app not specified, launch jupyter-labhub by default,
|
||||
# if jupyterlab is recent enough (3.1).
|
||||
# This is a minimally extended ServerApp that does:
|
||||
# 1. ensure lab extension is enabled, and
|
||||
# 2. set default URL to `/lab`
|
||||
import re
|
||||
|
||||
_version_pat = re.compile(r"(\d+)\.(\d+)")
|
||||
try:
|
||||
import jupyterlab
|
||||
from jupyterlab.labhubapp import SingleUserLabApp
|
||||
|
||||
m = _version_pat.match(jupyterlab.__version__)
|
||||
except Exception:
|
||||
m = None
|
||||
|
||||
if m is not None:
|
||||
version_tuple = tuple(int(v) for v in m.groups())
|
||||
if version_tuple >= (3, 1):
|
||||
return SingleUserLabApp.launch_instance()
|
||||
|
||||
return SingleUserNotebookApp.launch_instance()
|
||||
|
@@ -1,6 +1,10 @@
|
||||
"""Tests for jupyterhub.singleuser"""
|
||||
import os
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from subprocess import CalledProcessError
|
||||
from subprocess import check_output
|
||||
from unittest import mock
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import pytest
|
||||
@@ -14,6 +18,12 @@ from .utils import async_requests
|
||||
from .utils import AsyncSession
|
||||
|
||||
|
||||
@contextmanager
|
||||
def nullcontext():
|
||||
"""Python 3.7+ contextlib.nullcontext, backport for 3.6"""
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"access_scopes, server_name, expect_success",
|
||||
[
|
||||
@@ -171,3 +181,47 @@ def test_version():
|
||||
[sys.executable, '-m', 'jupyterhub.singleuser', '--version']
|
||||
).decode('utf8', 'replace')
|
||||
assert jupyterhub.__version__ in out
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"JUPYTERHUB_SINGLEUSER_APP",
|
||||
[
|
||||
"",
|
||||
"notebook.notebookapp.NotebookApp",
|
||||
"jupyter_server.serverapp.ServerApp",
|
||||
],
|
||||
)
|
||||
def test_singleuser_app_class(JUPYTERHUB_SINGLEUSER_APP):
|
||||
try:
|
||||
import jupyter_server # noqa
|
||||
except ImportError:
|
||||
have_server = False
|
||||
expect_error = "jupyter_server" in JUPYTERHUB_SINGLEUSER_APP
|
||||
else:
|
||||
have_server = True
|
||||
expect_error = False
|
||||
|
||||
if expect_error:
|
||||
ctx = pytest.raises(CalledProcessError)
|
||||
else:
|
||||
ctx = nullcontext()
|
||||
|
||||
with mock.patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"JUPYTERHUB_SINGLEUSER_APP": JUPYTERHUB_SINGLEUSER_APP,
|
||||
},
|
||||
):
|
||||
with ctx:
|
||||
out = check_output(
|
||||
[sys.executable, '-m', 'jupyterhub.singleuser', '--help-all']
|
||||
).decode('utf8', 'replace')
|
||||
if expect_error:
|
||||
return
|
||||
# use help-all output to check inheritance
|
||||
if 'NotebookApp' in JUPYTERHUB_SINGLEUSER_APP or not have_server:
|
||||
assert '--NotebookApp.' in out
|
||||
assert '--ServerApp.' not in out
|
||||
else:
|
||||
assert '--ServerApp.' in out
|
||||
assert '--NotebookApp.' not in out
|
||||
|
@@ -7,6 +7,6 @@ MAINTAINER Project Jupyter <jupyter@googlegroups.com>
|
||||
|
||||
ADD install_jupyterhub /tmp/install_jupyterhub
|
||||
ARG JUPYTERHUB_VERSION=main
|
||||
# install pinned jupyterhub and ensure notebook is installed
|
||||
# install pinned jupyterhub and ensure jupyterlab is installed
|
||||
RUN python3 /tmp/install_jupyterhub && \
|
||||
python3 -m pip install notebook
|
||||
python3 -m pip install jupyterlab
|
||||
|
Reference in New Issue
Block a user