mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-13 21:13: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
|
# Tests everything when JupyterHub works against a dedicated mysql or
|
||||||
# postgresql server.
|
# postgresql server.
|
||||||
#
|
#
|
||||||
# jupyter_server:
|
# nbclassic:
|
||||||
# Tests everything when the user instances are started with
|
# Tests everything when the user instances are started with
|
||||||
# jupyter_server instead of notebook.
|
# notebook instead of jupyter_server.
|
||||||
#
|
#
|
||||||
# ssl:
|
# ssl:
|
||||||
# Tests everything using internal SSL connections instead of
|
# Tests everything using internal SSL connections instead of
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
#
|
#
|
||||||
# main_dependencies:
|
# main_dependencies:
|
||||||
# Tests everything when the we use the latest available 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
|
# NOTE: Since only the value of these parameters are presented in the
|
||||||
# GitHub UI when the workflow run, we avoid using true/false as
|
# GitHub UI when the workflow run, we avoid using true/false as
|
||||||
@@ -56,6 +56,7 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- python: "3.6"
|
- python: "3.6"
|
||||||
oldest_dependencies: oldest_dependencies
|
oldest_dependencies: oldest_dependencies
|
||||||
|
nbclassic: nbclassic
|
||||||
- python: "3.6"
|
- python: "3.6"
|
||||||
subdomain: subdomain
|
subdomain: subdomain
|
||||||
- python: "3.7"
|
- python: "3.7"
|
||||||
@@ -65,7 +66,7 @@ jobs:
|
|||||||
- python: "3.8"
|
- python: "3.8"
|
||||||
db: postgres
|
db: postgres
|
||||||
- python: "3.8"
|
- python: "3.8"
|
||||||
jupyter_server: jupyter_server
|
nbclassic: nbclassic
|
||||||
- python: "3.9"
|
- python: "3.9"
|
||||||
main_dependencies: main_dependencies
|
main_dependencies: main_dependencies
|
||||||
|
|
||||||
@@ -130,9 +131,9 @@ jobs:
|
|||||||
if [ "${{ matrix.main_dependencies }}" != "" ]; then
|
if [ "${{ matrix.main_dependencies }}" != "" ]; then
|
||||||
pip install git+https://github.com/ipython/traitlets#egg=traitlets --force
|
pip install git+https://github.com/ipython/traitlets#egg=traitlets --force
|
||||||
fi
|
fi
|
||||||
if [ "${{ matrix.jupyter_server }}" != "" ]; then
|
if [ "${{ matrix.nbclassic }}" != "" ]; then
|
||||||
pip uninstall notebook --yes
|
pip uninstall jupyter_server --yes
|
||||||
pip install jupyter_server
|
pip install notebook
|
||||||
fi
|
fi
|
||||||
if [ "${{ matrix.db }}" == "mysql" ]; then
|
if [ "${{ matrix.db }}" == "mysql" ]; then
|
||||||
pip install mysql-connector-python
|
pip install mysql-connector-python
|
||||||
|
@@ -7,8 +7,8 @@ codecov
|
|||||||
coverage
|
coverage
|
||||||
cryptography
|
cryptography
|
||||||
html5lib # needed for beautifulsoup
|
html5lib # needed for beautifulsoup
|
||||||
|
jupyterlab >=3
|
||||||
mock
|
mock
|
||||||
notebook
|
|
||||||
pre-commit
|
pre-commit
|
||||||
pytest>=3.3
|
pytest>=3.3
|
||||||
pytest-asyncio
|
pytest-asyncio
|
||||||
|
@@ -76,13 +76,26 @@ c.InteractiveShellApp.extensions.append("cython")
|
|||||||
|
|
||||||
### Example: Enable a Jupyter notebook configuration setting for all users
|
### 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
|
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:
|
file:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# shutdown the server after no activity for an hour
|
# 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
|
# shutdown kernels after no activity for 20 minutes
|
||||||
c.MappingKernelManager.cull_idle_timeout = 20 * 60
|
c.MappingKernelManager.cull_idle_timeout = 20 * 60
|
||||||
# check for idle kernels every two minutes
|
# 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:
|
sure are available, I can install their specs system-wide (in /usr/local) with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/path/to/python3 -m IPython kernel install --prefix=/usr/local
|
/path/to/python3 -m ipykernel install --prefix=/usr/local
|
||||||
/path/to/python2 -m IPython kernel install --prefix=/usr/local
|
/path/to/python2 -m ipykernel install --prefix=/usr/local
|
||||||
```
|
```
|
||||||
|
|
||||||
## Multi-user hosts vs. Containers
|
## 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
|
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
|
```bash
|
||||||
export JUPYTERHUB_SINGLEUSER_APP='jupyter_server.serverapp.ServerApp'
|
export JUPYTERHUB_SINGLEUSER_APP='jupyter_server.serverapp.ServerApp'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
@@ -1,7 +1,12 @@
|
|||||||
"""Make a single-user app based on the environment:
|
"""Make a single-user app based on the environment:
|
||||||
|
|
||||||
- $JUPYTERHUB_SINGLEUSER_APP, the base Application class, to be wrapped in JupyterHub authentication.
|
- $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
|
import os
|
||||||
|
|
||||||
@@ -9,12 +14,55 @@ from traitlets import import_item
|
|||||||
|
|
||||||
from .mixins import make_singleuser_app
|
from .mixins import make_singleuser_app
|
||||||
|
|
||||||
JUPYTERHUB_SINGLEUSER_APP = (
|
JUPYTERHUB_SINGLEUSER_APP = os.environ.get("JUPYTERHUB_SINGLEUSER_APP")
|
||||||
os.environ.get("JUPYTERHUB_SINGLEUSER_APP") or "notebook.notebookapp.NotebookApp"
|
|
||||||
)
|
|
||||||
|
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
|
||||||
|
|
||||||
App = import_item(JUPYTERHUB_SINGLEUSER_APP)
|
|
||||||
|
|
||||||
SingleUserNotebookApp = make_singleuser_app(App)
|
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"""
|
"""Tests for jupyterhub.singleuser"""
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from subprocess import CalledProcessError
|
||||||
from subprocess import check_output
|
from subprocess import check_output
|
||||||
|
from unittest import mock
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -14,6 +18,12 @@ from .utils import async_requests
|
|||||||
from .utils import AsyncSession
|
from .utils import AsyncSession
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def nullcontext():
|
||||||
|
"""Python 3.7+ contextlib.nullcontext, backport for 3.6"""
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"access_scopes, server_name, expect_success",
|
"access_scopes, server_name, expect_success",
|
||||||
[
|
[
|
||||||
@@ -171,3 +181,47 @@ def test_version():
|
|||||||
[sys.executable, '-m', 'jupyterhub.singleuser', '--version']
|
[sys.executable, '-m', 'jupyterhub.singleuser', '--version']
|
||||||
).decode('utf8', 'replace')
|
).decode('utf8', 'replace')
|
||||||
assert jupyterhub.__version__ in out
|
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
|
ADD install_jupyterhub /tmp/install_jupyterhub
|
||||||
ARG JUPYTERHUB_VERSION=main
|
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 && \
|
RUN python3 /tmp/install_jupyterhub && \
|
||||||
python3 -m pip install notebook
|
python3 -m pip install jupyterlab
|
||||||
|
Reference in New Issue
Block a user