diff --git a/jupyterhub/singleuser/__init__.py b/jupyterhub/singleuser/__init__.py index b7aa5b15..0b06e2f6 100644 --- a/jupyterhub/singleuser/__init__.py +++ b/jupyterhub/singleuser/__init__.py @@ -4,3 +4,10 @@ Contains default notebook-app subclass and mixins """ from .app import main from .app import SingleUserNotebookApp +from .mixins import HubAuthenticatedHandler +from .mixins import make_singleuser_app + +# backward-compatibility +JupyterHubLoginHandler = SingleUserNotebookApp.login_handler_class +JupyterHubLogoutHandler = SingleUserNotebookApp.logout_handler_class +OAuthCallbackHandler = SingleUserNotebookApp.oauth_callback_handler_class diff --git a/jupyterhub/singleuser/app.py b/jupyterhub/singleuser/app.py index cee6d9f3..3afe6fe1 100644 --- a/jupyterhub/singleuser/app.py +++ b/jupyterhub/singleuser/app.py @@ -2,12 +2,6 @@ - $JUPYTERHUB_SINGLEUSER_APP, the base Application class, to be wrapped in JupyterHub authentication. default: notebook.notebookapp.NotebookApp -- $JUPYTERHUB_SINGLEUSER_PKG, e.g. notebook or jupyter_server. - Typically inferred from $JUPYTEHUB_SINGLEUSER_APP. - Package layout must include: - - base.handlers.JupyterHandler - - auth.login.LoginHandler - - auth.logout.LogoutHandler """ import os @@ -18,38 +12,9 @@ from .mixins import make_singleuser_app JUPYTERHUB_SINGLEUSER_APP = ( os.environ.get("JUPYTERHUB_SINGLEUSER_APP") or "notebook.notebookapp.NotebookApp" ) -JUPYTERHUB_SINGLEUSER_PKG = os.environ.get("JUPYTERHUB_SINGLEUSER_PKG") or "notebook" App = import_item(JUPYTERHUB_SINGLEUSER_APP) -JUPYTERHUB_SINGLEUSER_PKG = os.environ.get("JUPYTERHUB_SINGLEUSER_PKG") -if not JUPYTERHUB_SINGLEUSER_PKG: - # guess notebook or jupyter_server based on App class - for cls in App.mro(): - pkg = cls.__module__.split(".", 1)[0] - if pkg in {"notebook", "jupyter_server"}: - JUPYTERHUB_SINGLEUSER_PKG = pkg - break - else: - raise RuntimeError( - "Failed to infer JUPYTERHUB_SINGLEUSER_PKG from {}, please set it directly".format( - JUPYTERHUB_SINGLEUSER_APP - ) - ) - -LoginHandler = import_item(pkg + ".auth.login.LoginHandler") -LogoutHandler = import_item(pkg + ".auth.logout.LogoutHandler") -# BaseHandler could be called JupyterHandler or old IPythonHandler -try: - BaseHandler = import_item(pkg + ".base.handlers.JupyterHandler") -except ImportError: - BaseHandler = import_item(pkg + ".base.handlers.IPythonHandler") - -SingleUserNotebookApp = make_singleuser_app( - App, - LoginHandler=LoginHandler, - LogoutHandler=LogoutHandler, - BaseHandler=BaseHandler, -) +SingleUserNotebookApp = make_singleuser_app(App) main = SingleUserNotebookApp.launch_instance diff --git a/jupyterhub/singleuser/mixins.py b/jupyterhub/singleuser/mixins.py index 7d16581b..1703e9a0 100755 --- a/jupyterhub/singleuser/mixins.py +++ b/jupyterhub/singleuser/mixins.py @@ -31,6 +31,7 @@ from traitlets import Bool from traitlets import Bytes from traitlets import CUnicode from traitlets import default +from traitlets import import_item from traitlets import Integer from traitlets import observe from traitlets import TraitError @@ -208,9 +209,6 @@ def _exclude_home(path_list): yield p -from traitlets import HasTraits - - class SingleUserNotebookAppMixin(Configurable): """A Subclass of the regular NotebookApp that is aware of the parent multiuser context.""" @@ -660,23 +658,57 @@ class SingleUserNotebookAppMixin(Configurable): env.loader = ChoiceLoader([FunctionLoader(get_page), orig_loader]) -def make_singleuser_app(App, LoginHandler, LogoutHandler, BaseHandler): +def detect_base_package(App): + """Detect the base package for an App class + + Will return 'notebook' or 'jupyter_server' + based on which package App subclasses from. + + Will return None if neither is identified (e.g. fork package, or duck-typing). + """ + # guess notebook or jupyter_server based on App class inheritance + for cls in App.mro(): + pkg = cls.__module__.split(".", 1)[0] + if pkg in {"notebook", "jupyter_server"}: + return pkg + return None + + +def make_singleuser_app(App): """Make and return a singleuser notebook app - given existing notebook or jupyter_server classes, + given existing notebook or jupyter_server Application classes, mix-in jupyterhub auth. + Instances of App must have the following attributes defining classes: + + - .login_handler_class + - .logout_handler_class + - .base_handler_class (only required if not a subclass of the default app + in jupyter_server or notebook) + App should be a subclass of `notebook.notebookapp.NotebookApp` - or `jupyter_server.serverapp.ServerApp` - - Must be passed base classes for: - - - App - - LoginHandler - - LogoutHandler - - BaseHandler + or `jupyter_server.serverapp.ServerApp`. """ - # create handler classes from mixins + bases + + empty_parent_app = App() + + # detect base classes + LoginHandler = empty_parent_app.login_handler_class + LogoutHandler = empty_parent_app.logout_handler_class + BaseHandler = getattr(empty_parent_app, "base_handler_class", None) + if BaseHandler is None: + pkg = detect_base_package(App) + if pkg == "jupyter_server": + BaseHandler = import_item("jupyter_server.base.handlers.JupyterHandler") + elif pkg == "notebook": + BaseHandler = import_item("notebook.base.handlers.IPythonHandler") + else: + raise ValueError( + "{}.base_handler_class must be defined".format(App.__name__) + ) + + # create Handler classes from mixins + bases class JupyterHubLoginHandler(JupyterHubLoginHandlerMixin, LoginHandler): pass @@ -687,13 +719,12 @@ def make_singleuser_app(App, LoginHandler, LogoutHandler, BaseHandler): pass # create merged aliases & flags - empty_parent_app = App() merged_aliases = {} - merged_aliases.update(empty_parent_app.aliases) + merged_aliases.update(empty_parent_app.aliases or {}) merged_aliases.update(aliases) merged_flags = {} - merged_flags.update(empty_parent_app.flags) + merged_flags.update(empty_parent_app.flags or {}) merged_flags.update(flags) # create mixed-in App class, bringing it all together class SingleUserNotebookApp(SingleUserNotebookAppMixin, App): diff --git a/jupyterhub/singleuser/notebookapp.py b/jupyterhub/singleuser/notebookapp.py deleted file mode 100644 index 22d0e6b1..00000000 --- a/jupyterhub/singleuser/notebookapp.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python -"""Extend regular notebook server to be aware of multiuser things.""" -# Copyright (c) Jupyter Development Team. -# Distributed under the terms of the Modified BSD License. -from notebook.auth.login import LoginHandler -from notebook.auth.logout import LogoutHandler -from notebook.base.handlers import IPythonHandler -from notebook.notebookapp import NotebookApp - -from .mixins import make_singleuser_app - -SingleUserNotebookApp = make_singleuser_app( - NotebookApp=NotebookApp, - LoginHandler=LoginHandler, - LogoutHandler=LogoutHandler, - BaseHandler=IPythonHandler, -) - -main = SingleUserNotebookApp.launch_instance - -if __name__ == '__main__': - main()