21
21
class ExtensionPoint (HasTraits ):
22
22
"""A simple API for connecting to a Jupyter Server extension
23
23
point defined by metadata and importable from a Python package.
24
-
25
- Usage:
26
-
27
- metadata = {
28
- "module": "extension_module",
29
- "":
30
- }
31
-
32
- point = ExtensionPoint(metadata)
33
24
"""
34
25
metadata = Dict ()
35
26
@@ -58,6 +49,10 @@ def _valid_metadata(self, proposed):
58
49
metadata ["app" ] = app ()
59
50
return metadata
60
51
52
+ @property
53
+ def linked (self ):
54
+ return self ._linked
55
+
61
56
@property
62
57
def app (self ):
63
58
"""If the metadata includes an `app` field"""
@@ -103,7 +98,10 @@ def link(self, serverapp):
103
98
# Otherwise return a dummy function.
104
99
lambda serverapp : None
105
100
)
106
- return linker (serverapp )
101
+ # Capture output to return
102
+ out = linker (serverapp )
103
+ # Store that this extension has been linked
104
+ return out
107
105
108
106
def load (self , serverapp ):
109
107
"""Load the extension in a Jupyter ServerApp object.
@@ -130,6 +128,9 @@ class ExtensionPackage(HasTraits):
130
128
"""
131
129
name = Unicode (help = "Name of the an importable Python package." )
132
130
131
+ # A dictionary that stores whether the extension point has been linked.
132
+ _linked_points = {}
133
+
133
134
@validate ("name" )
134
135
def _validate_name (self , proposed ):
135
136
name = proposed ['value' ]
@@ -157,85 +158,96 @@ def extension_points(self):
157
158
"""A dictionary of extension points."""
158
159
return self ._extension_points
159
160
161
+ def link_point (self , point_name , serverapp ):
162
+ linked = self ._linked_points .get (point_name , False )
163
+ if not linked :
164
+ point = self .extension_points [point_name ]
165
+ point .link (serverapp )
166
+
167
+ def load_point (self , point_name , serverapp ):
168
+ point = self .extension_points [point_name ]
169
+ point .load (serverapp )
170
+
171
+ def link_all_points (self , serverapp ):
172
+ for point_name in self .extension_points :
173
+ self .link_point (point_name , serverapp )
174
+
175
+ def load_all_points (self , serverapp ):
176
+ for point_name in self .extension_points :
177
+ self .load_point (point_name , serverapp )
178
+
160
179
161
180
class ExtensionManager (LoggingConfigurable ):
162
181
"""High level interface for findind, validating,
163
182
linking, loading, and managing Jupyter Server extensions.
164
183
165
184
Usage:
166
-
167
185
m = ExtensionManager(jpserver_extensions=extensions)
168
186
"""
169
- parent = Instance (
170
- klass = "jupyter_server.serverapp.ServerApp" ,
171
- allow_none = True
172
- )
173
-
174
- jpserver_extensions = Dict (
175
- help = (
176
- "A dictionary with extension package names "
177
- "as keys and booleans to enable as values."
178
- )
179
- )
180
-
181
- @default ('jpserver_extensions' )
182
- def _default_jpserver_extensions (self ):
183
- return self .parent .jpserver_extensions
184
-
185
- @validate ('jpserver_extensions' )
186
- def _validate_jpserver_extensions (self , proposed ):
187
- jpserver_extensions = proposed ['value' ]
188
- self ._extensions = {}
189
- # Iterate over dictionary items and validate that
190
- # we can interface with each extension. If the extension
191
- # fails to interface, throw a warning through the logger
192
- # interface.
193
- for package_name , enabled in jpserver_extensions .items ():
194
- if enabled :
195
- try :
196
- self ._extensions [package_name ] = ExtensionPackage (
197
- name = package_name
198
- )
199
- # Raise a warning if the extension cannot be loaded.
200
- except Exception as e :
201
- self .log .warning (e )
202
- return jpserver_extensions
187
+ # The `enabled_extensions` attribute provides a dictionary
188
+ # with extension names mapped to their ExtensionPackage interface
189
+ # (see above). This manager simplifies the interaction between the
190
+ # ServerApp and the extensions being appended.
191
+ _enabled_extensions = {}
192
+ # The `_linked_extensions` attribute tracks when each extension
193
+ # has been successfully linked to a ServerApp. This helps prevent
194
+ # extensions from being re-linked recursively unintentionally if another
195
+ # extension attempts to link extensions again.
196
+ _linked_extensions = {}
203
197
204
198
@property
205
- def extensions (self ):
199
+ def enabled_extensions (self ):
206
200
"""Dictionary with extension package names as keys
207
201
and an ExtensionPackage objects as values.
208
202
"""
209
- return self ._extensions
203
+ return dict ( sorted ( self ._enabled_extensions . items ()))
210
204
211
- @property
212
- def extension_points (self ):
213
- points = {}
214
- for ext in self .extensions .values ():
215
- points .update (ext .extension_points )
216
- return points
205
+ def from_jpserver_extensions (self , jpserver_extensions ):
206
+ """Add extensions from 'jpserver_extensions'-like dictionary."""
207
+ for name , enabled in jpserver_extensions .items ():
208
+ if enabled :
209
+ self .add_extension (name )
210
+
211
+ def add_extension (self , extension_name ):
212
+ try :
213
+ extpkg = ExtensionPackage (name = extension_name )
214
+ self ._enabled_extensions [extension_name ] = extpkg
215
+ # Raise a warning if the extension cannot be loaded.
216
+ except Exception as e :
217
+ self .log .warning (e )
218
+
219
+ def link_extension (self , name , serverapp ):
220
+ linked = self ._linked_extensions .get (name , False )
221
+ extension = self .enabled_extensions [name ]
222
+ if not linked :
223
+ try :
224
+ extension .link_all_points (serverapp )
225
+ self .log .debug ("The '{}' extension was successfully linked." .format (name ))
226
+ except Exception as e :
227
+ self .log .warning (e )
228
+
229
+ def load_extension (self , name , serverapp ):
230
+ extension = self .enabled_extensions .get (name )
231
+ try :
232
+ extension .load_all_points (serverapp )
233
+ except Exception as e :
234
+ self .log .warning (e )
217
235
218
- def link_extensions (self , serverapp ):
236
+ def link_all_extensions (self , serverapp ):
219
237
"""Link all enabled extensions
220
- to an instance of ServerApp
238
+ to an instance of ServerApp
221
239
"""
222
240
# Sort the extension names to enforce deterministic linking
223
241
# order.
224
- for name , ext in sorted (self .extension_points .items ()):
225
- try :
226
- ext .link (serverapp )
227
- except Exception as e :
228
- self .log .warning (e )
242
+ for name in self .enabled_extensions :
243
+ self .link_extension (name , serverapp )
229
244
230
- def load_extensions (self , serverapp ):
245
+ def load_all_extensions (self , serverapp ):
231
246
"""Load all enabled extensions and append them to
232
247
the parent ServerApp.
233
248
"""
234
249
# Sort the extension names to enforce deterministic loading
235
250
# order.
236
- for name , ext in sorted (self .extension_points .items ()):
237
- try :
238
- ext .load (serverapp )
239
- except Exception as e :
240
- self .log .warning (e )
251
+ for name in self .enabled_extensions :
252
+ self .load_extension (name , serverapp )
241
253
0 commit comments