-
Notifications
You must be signed in to change notification settings - Fork 11
/
nbserver.py
141 lines (115 loc) · 5.07 KB
/
nbserver.py
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
"""
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