File size: 5,189 Bytes
d1ceb73 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
"""
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)
# First check if this thing is a trait (see traitlets)
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)
# Anything else appended on the class is just an attribute of the class.
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):
# Get the extension manager from the server
manager = serverapp.extension_manager
logger = serverapp.log
# Hack that patches the enabled extensions list, prioritizing
# jupyter nbclassic. In the future, it would be much better
# to incorporate a dependency injection system in the
# Extension manager that allows extensions to list
# their dependency tree and sort that way.
def sorted_extensions(self):
"""Dictionary with extension package names as keys
and an ExtensionPackage objects as values.
"""
# Sort the keys and
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)
# Look to see if nbclassic is enabled. if so,
# link the nbclassic extension here to load
# its config. Then, port its config to the serverapp
# for backwards compatibility.
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()
# Proxy NotebookApp traits through serverapp to notebookapp.
members = diff_members(serverapp, nbapp)
for m in members:
proxy(serverapp, nbapp, m)
# Find jupyter server extensions listed as notebook server extensions.
jupyter_paths = jupyter_config_path()
config_dirs = jupyter_paths + [serverapp.config_dir]
nbserver_extensions = get_nbserver_extensions(config_dirs)
# Link all extensions found in the old locations for
# notebook server extensions.
for name, enabled in nbserver_extensions.items():
# If the extension is already enabled in the manager, i.e.
# because it was discovered already by Jupyter Server
# through its jupyter_server_config, then don't re-enable here.
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):
# Patch the config service manager to find the
# proper path for old notebook frontend extensions
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
|