|
""" |
|
This module contains a Jupyter Server extension that attempts to |
|
make classic server and notebook extensions work in the new server. |
|
|
|
Unfortunately, you'll notice that requires some major monkey-patching. |
|
The goal is that this extension will only be used as a temporary |
|
patch to transition extension authors from classic notebook server to jupyter_server. |
|
""" |
|
import os |
|
import types |
|
import inspect |
|
from functools import wraps |
|
from jupyter_core.paths import jupyter_config_path |
|
from traitlets.traitlets import is_trait |
|
|
|
|
|
from jupyter_server.services.config.manager import ConfigManager |
|
from .traits import NotebookAppTraits |
|
|
|
|
|
class ClassProxyError(Exception): |
|
pass |
|
|
|
|
|
def proxy(obj1, obj2, name, overwrite=False): |
|
"""Redirects a method, property, or trait from object 1 to object 2.""" |
|
if hasattr(obj1, name) and overwrite is False: |
|
raise ClassProxyError( |
|
"Cannot proxy the attribute '{name}' from {cls2} because " |
|
"{cls1} already has this attribute.".format( |
|
name=name, |
|
cls1=obj1.__class__, |
|
cls2=obj2.__class__ |
|
) |
|
) |
|
attr = getattr(obj2, name) |
|
|
|
|
|
cls_attr = getattr(obj2.__class__, name) |
|
if is_trait(cls_attr) or type(attr) == property: |
|
thing = property(lambda self: getattr(obj2, name)) |
|
|
|
elif isinstance(attr, types.MethodType): |
|
@wraps(attr) |
|
def thing(self, *args, **kwargs): |
|
return attr(*args, **kwargs) |
|
|
|
|
|
else: |
|
thing = attr |
|
|
|
setattr(obj1.__class__, name, thing) |
|
|
|
|
|
def public_members(obj): |
|
members = inspect.getmembers(obj) |
|
return [m for m, _ in members if not m.startswith('_')] |
|
|
|
|
|
def diff_members(obj1, obj2): |
|
"""Return all attribute names found in obj2 but not obj1""" |
|
m1 = public_members(obj1) |
|
m2 = public_members(obj2) |
|
return set(m2).difference(m1) |
|
|
|
|
|
def get_nbserver_extensions(config_dirs): |
|
cm = ConfigManager(read_config_path=config_dirs) |
|
section = cm.get("jupyter_notebook_config") |
|
extensions = section.get('NotebookApp', {}).get('nbserver_extensions', {}) |
|
return extensions |
|
|
|
|
|
def _link_jupyter_server_extension(serverapp): |
|
|
|
manager = serverapp.extension_manager |
|
logger = serverapp.log |
|
|
|
|
|
|
|
|
|
|
|
|
|
def sorted_extensions(self): |
|
"""Dictionary with extension package names as keys |
|
and an ExtensionPackage objects as values. |
|
""" |
|
|
|
keys = sorted(self.extensions.keys()) |
|
keys.remove("notebook_shim") |
|
keys = ["notebook_shim"] + keys |
|
return {key: self.extensions[key] for key in keys} |
|
|
|
manager.__class__.sorted_extensions = property(sorted_extensions) |
|
|
|
|
|
|
|
|
|
|
|
try: |
|
pkg = manager.extensions["notebook_shim"] |
|
pkg.link_point("notebook_shim", serverapp) |
|
point = pkg.extension_points["notebook_shim"] |
|
nbapp = point.app |
|
except Exception: |
|
nbapp = NotebookAppTraits() |
|
|
|
|
|
members = diff_members(serverapp, nbapp) |
|
for m in members: |
|
proxy(serverapp, nbapp, m) |
|
|
|
|
|
jupyter_paths = jupyter_config_path() |
|
config_dirs = jupyter_paths + [serverapp.config_dir] |
|
nbserver_extensions = get_nbserver_extensions(config_dirs) |
|
|
|
|
|
|
|
for name, enabled in nbserver_extensions.items(): |
|
|
|
|
|
|
|
if name not in manager.extensions: |
|
successful = manager.add_extension(name, enabled=enabled) |
|
if successful: |
|
logger.info( |
|
"{name} | extension was found and enabled by notebook_shim. " |
|
"Consider moving the extension to Jupyter Server's " |
|
"extension paths.".format(name=name) |
|
) |
|
manager.link_extension(name) |
|
|
|
def _load_jupyter_server_extension(serverapp): |
|
|
|
|
|
config_manager = serverapp.config_manager |
|
read_config_path = config_manager.read_config_path |
|
read_config_path += [os.path.join(p, 'nbconfig') |
|
for p in jupyter_config_path()] |
|
config_manager.read_config_path = read_config_path |
|
|