0
# Extensions
1
2
The Jupyter Server extension system provides a powerful framework for building pluggable server applications and adding functionality to existing servers.
3
4
## Extension Development
5
6
### ExtensionApp
7
8
Base class for creating server extensions that can run standalone or integrate with existing servers.
9
10
```python
11
from jupyter_server.extension.application import ExtensionApp
12
```
13
14
#### Basic Extension
15
16
```python{ .api }
17
from jupyter_server.extension.application import ExtensionApp
18
from jupyter_server.base.handlers import JupyterHandler
19
20
class HelloHandler(JupyterHandler):
21
def get(self):
22
self.finish({"message": "Hello from my extension!"})
23
24
class MyExtension(ExtensionApp):
25
name = "my_extension"
26
description = "My custom Jupyter extension"
27
version = "1.0.0"
28
29
# Extension metadata
30
extension_url = "/my_extension"
31
load_other_extensions = True
32
33
def initialize_handlers(self):
34
"""Register URL handlers for this extension."""
35
handlers = [
36
(r"/my_extension/hello", HelloHandler),
37
]
38
self.handlers.extend(handlers)
39
40
def initialize_settings(self):
41
"""Initialize custom settings."""
42
self.settings.update({
43
"my_extension_config": self.config,
44
})
45
```
46
47
#### Advanced Extension with Templates
48
49
```python{ .api }
50
from jupyter_server.extension.application import ExtensionAppJinjaMixin, ExtensionApp
51
from jupyter_server.base.handlers import JupyterHandler
52
53
class MyTemplateHandler(JupyterHandler):
54
def get(self):
55
# Render template with context
56
html = self.render_template(
57
"my_template.html",
58
title="My Extension",
59
data={"key": "value"}
60
)
61
self.finish(html)
62
63
class MyExtensionWithTemplates(ExtensionAppJinjaMixin, ExtensionApp):
64
name = "my_templated_extension"
65
66
def initialize_templates(self):
67
"""Setup Jinja2 template environment."""
68
self.initialize_jinja2_settings()
69
70
# Add custom template paths
71
template_paths = [
72
os.path.join(os.path.dirname(__file__), "templates"),
73
]
74
self.settings["jinja2_env"].loader.searchpath.extend(template_paths)
75
76
def initialize_handlers(self):
77
handlers = [
78
(r"/my_extension/page", MyTemplateHandler),
79
]
80
self.handlers.extend(handlers)
81
```
82
83
### Extension Registration
84
85
#### Setup Function
86
87
```python{ .api }
88
# In your extension package's __init__.py or main module
89
90
def _load_jupyter_server_extension(serverapp):
91
"""Load the extension into a Jupyter server."""
92
extension = MyExtension()
93
extension.serverapp = serverapp
94
extension.load_config_file()
95
extension.initialize()
96
97
return extension
98
99
# Optional: Define extension metadata
100
def _jupyter_server_extension_paths():
101
"""Return list of extension paths."""
102
return [
103
{
104
"module": "my_extension",
105
"app": MyExtension,
106
}
107
]
108
```
109
110
#### Package Metadata
111
112
```python
113
# In setup.py or pyproject.toml
114
setup(
115
name="my-extension",
116
entry_points={
117
"jupyter_server.extension": [
118
"my_extension = my_extension:_load_jupyter_server_extension",
119
],
120
},
121
)
122
```
123
124
## Extension Management
125
126
### ExtensionManager
127
128
Manages the lifecycle of extensions in a server instance.
129
130
```python{ .api }
131
from jupyter_server.extension.manager import ExtensionManager, ExtensionPackage
132
133
# Create extension manager
134
manager = ExtensionManager()
135
136
# Enable extension
137
manager.enable_extension("my_extension")
138
139
# Disable extension
140
manager.disable_extension("my_extension")
141
142
# List enabled extensions
143
enabled = manager.enabled_extensions
144
print(enabled) # {"my_extension": True}
145
146
# Check if extension is enabled
147
is_enabled = manager.is_extension_enabled("my_extension")
148
```
149
150
### ExtensionPackage
151
152
Represents metadata about an extension package.
153
154
```python{ .api }
155
from jupyter_server.extension.manager import ExtensionPackage
156
157
# Create extension package info
158
package = ExtensionPackage(
159
name="my_extension",
160
enabled=True,
161
module="my_extension",
162
app=MyExtension,
163
)
164
165
# Access metadata
166
print(package.name) # "my_extension"
167
print(package.version) # Extension version
168
print(package.description) # Extension description
169
print(package.enabled) # True/False
170
```
171
172
## CLI Management
173
174
### ServerExtensionApp
175
176
Command-line interface for managing extensions.
177
178
```python{ .api }
179
from jupyter_server.extension.serverextension import (
180
ServerExtensionApp,
181
EnableServerExtensionApp,
182
DisableServerExtensionApp,
183
ListServerExtensionsApp,
184
)
185
186
# Enable extension via CLI
187
enable_app = EnableServerExtensionApp()
188
enable_app.initialize(["my_extension"])
189
enable_app.start()
190
191
# Disable extension via CLI
192
disable_app = DisableServerExtensionApp()
193
disable_app.initialize(["my_extension"])
194
disable_app.start()
195
196
# List extensions via CLI
197
list_app = ListServerExtensionsApp()
198
list_app.initialize()
199
list_app.start()
200
```
201
202
### Command Line Usage
203
204
```bash
205
# Enable extension system-wide
206
jupyter server extension enable my_extension
207
208
# Enable for current user only
209
jupyter server extension enable my_extension --user
210
211
# Enable for specific Python environment
212
jupyter server extension enable my_extension --sys-prefix
213
214
# Disable extension
215
jupyter server extension disable my_extension
216
217
# List all extensions
218
jupyter server extension list
219
```
220
221
## Extension Utilities
222
223
### Extension Discovery
224
225
```python{ .api }
226
from jupyter_server.extension.utils import (
227
get_loader,
228
get_metadata,
229
validate_extension,
230
)
231
232
# Get extension loader function
233
loader = get_loader("my_extension")
234
if loader:
235
extension = loader(serverapp)
236
237
# Extract extension metadata
238
metadata = get_metadata("my_extension")
239
print(metadata) # Extension info dict
240
241
# Validate extension structure
242
try:
243
validate_extension("my_extension")
244
print("Extension is valid")
245
except Exception as e:
246
print(f"Extension validation failed: {e}")
247
```
248
249
### Extension Configuration
250
251
```python{ .api }
252
from jupyter_server.extension.config import ExtensionConfigManager
253
254
# Create config manager for extension
255
config_manager = ExtensionConfigManager(extension_name="my_extension")
256
257
# Get extension-specific config
258
config = config_manager.get("my_section")
259
260
# Set configuration values
261
config_manager.set("my_section", "setting", "value")
262
263
# Update configuration
264
config_manager.update("my_section", {
265
"setting1": "value1",
266
"setting2": "value2",
267
})
268
```
269
270
## Extension Handlers
271
272
### ExtensionHandlerMixin
273
274
Base mixin for extension handlers with common functionality.
275
276
```python{ .api }
277
from jupyter_server.extension.handler import ExtensionHandlerMixin
278
from jupyter_server.base.handlers import JupyterHandler
279
280
class MyExtensionHandler(ExtensionHandlerMixin, JupyterHandler):
281
"""Handler for my extension endpoints."""
282
283
def get(self):
284
# Access extension-specific settings
285
extension_name = self.extension_name
286
config = self.extension_config
287
288
self.finish({
289
"extension": extension_name,
290
"config": config,
291
})
292
```
293
294
### Template Support
295
296
```python{ .api }
297
from jupyter_server.extension.handler import ExtensionHandlerJinjaMixin
298
from jupyter_server.base.handlers import JupyterHandler
299
300
class MyTemplatedHandler(ExtensionHandlerJinjaMixin, JupyterHandler):
301
"""Handler with Jinja2 template support."""
302
303
def get(self):
304
# Render extension template
305
html = self.render_template(
306
"my_extension/page.html",
307
title="My Extension Page",
308
data=self.get_extension_data(),
309
)
310
self.finish(html)
311
312
def get_extension_data(self):
313
"""Get data to pass to template."""
314
return {
315
"version": self.extension_version,
316
"config": self.extension_config,
317
}
318
```
319
320
## Advanced Extension Patterns
321
322
### Extension with Custom Services
323
324
```python{ .api }
325
from jupyter_server.extension.application import ExtensionApp
326
from jupyter_server.base.handlers import APIHandler
327
from traitlets import Instance, Unicode
328
329
class MyServiceManager:
330
"""Custom service for the extension."""
331
332
def __init__(self, config=None):
333
self.config = config or {}
334
335
async def get_data(self):
336
"""Get service data."""
337
return {"status": "active", "data": "service_data"}
338
339
class MyServiceHandler(APIHandler):
340
"""API handler for the custom service."""
341
342
def initialize(self, service_manager):
343
self.service_manager = service_manager
344
345
async def get(self):
346
data = await self.service_manager.get_data()
347
self.finish(data)
348
349
class MyServiceExtension(ExtensionApp):
350
name = "my_service_extension"
351
352
# Custom service as a trait
353
service_manager = Instance(MyServiceManager)
354
355
def initialize_services(self):
356
"""Initialize custom services."""
357
self.service_manager = MyServiceManager(config=self.config)
358
359
def initialize_handlers(self):
360
handlers = [
361
(
362
r"/my_service/api/data",
363
MyServiceHandler,
364
{"service_manager": self.service_manager}
365
),
366
]
367
self.handlers.extend(handlers)
368
```
369
370
### Extension with WebSocket Support
371
372
```python{ .api }
373
from jupyter_server.extension.application import ExtensionApp
374
from jupyter_server.base.zmqhandlers import WebSocketMixin
375
from tornado import websocket
376
import json
377
378
class MyWebSocketHandler(WebSocketMixin, websocket.WebSocketHandler):
379
"""WebSocket handler for real-time communication."""
380
381
def open(self):
382
"""Handle WebSocket connection."""
383
self.log.info("WebSocket connection opened")
384
385
def on_message(self, message):
386
"""Handle incoming WebSocket message."""
387
try:
388
data = json.loads(message)
389
# Process message
390
response = {"type": "response", "data": data}
391
self.write_message(json.dumps(response))
392
except json.JSONDecodeError:
393
self.write_message(json.dumps({
394
"type": "error",
395
"message": "Invalid JSON"
396
}))
397
398
def on_close(self):
399
"""Handle WebSocket disconnection."""
400
self.log.info("WebSocket connection closed")
401
402
class MyWebSocketExtension(ExtensionApp):
403
name = "my_websocket_extension"
404
405
def initialize_handlers(self):
406
handlers = [
407
(r"/my_extension/ws", MyWebSocketHandler),
408
]
409
self.handlers.extend(handlers)
410
```
411
412
### Multi-Extension Package
413
414
```python{ .api }
415
from jupyter_server.extension.application import ExtensionApp
416
417
class BaseExtension(ExtensionApp):
418
"""Base extension with shared functionality."""
419
420
def get_common_settings(self):
421
return {
422
"base_url": self.base_url,
423
"common_config": self.config.get("common", {}),
424
}
425
426
class ExtensionA(BaseExtension):
427
name = "extension_a"
428
429
def initialize_handlers(self):
430
# Extension A specific handlers
431
pass
432
433
class ExtensionB(BaseExtension):
434
name = "extension_b"
435
436
def initialize_handlers(self):
437
# Extension B specific handlers
438
pass
439
440
# Registration functions
441
def _load_jupyter_server_extension(serverapp):
442
"""Load all extensions in this package."""
443
extensions = []
444
445
for ext_class in [ExtensionA, ExtensionB]:
446
ext = ext_class()
447
ext.serverapp = serverapp
448
ext.load_config_file()
449
ext.initialize()
450
extensions.append(ext)
451
452
return extensions
453
454
def _jupyter_server_extension_paths():
455
"""Return extension paths for discovery."""
456
return [
457
{"module": "my_multi_extension.extension_a", "app": ExtensionA},
458
{"module": "my_multi_extension.extension_b", "app": ExtensionB},
459
]
460
```
461
462
## Testing Extensions
463
464
### Extension Test Utilities
465
466
```python{ .api }
467
import pytest
468
from jupyter_server.extension.application import ExtensionApp
469
from jupyter_server.serverapp import ServerApp
470
471
@pytest.fixture
472
def extension_app():
473
"""Create extension app for testing."""
474
app = MyExtension()
475
app.serverapp = ServerApp()
476
app.initialize()
477
return app
478
479
def test_extension_handlers(extension_app):
480
"""Test extension handler registration."""
481
assert len(extension_app.handlers) > 0
482
483
# Check specific handler exists
484
handler_patterns = [handler[0] for handler in extension_app.handlers]
485
assert "/my_extension/api" in handler_patterns
486
487
def test_extension_config(extension_app):
488
"""Test extension configuration."""
489
assert extension_app.name == "my_extension"
490
assert hasattr(extension_app, "config")
491
```
492
493
## Extension Exceptions
494
495
```python{ .api }
496
from jupyter_server.extension.utils import (
497
ExtensionLoadingError,
498
ExtensionMetadataError,
499
ExtensionModuleNotFound,
500
NotAnExtensionApp,
501
)
502
503
try:
504
loader = get_loader("nonexistent_extension")
505
except ExtensionModuleNotFound as e:
506
print(f"Extension module not found: {e}")
507
508
try:
509
validate_extension("invalid_extension")
510
except ExtensionMetadataError as e:
511
print(f"Extension metadata error: {e}")
512
513
try:
514
# Load extension that isn't properly structured
515
extension = loader(serverapp)
516
except NotAnExtensionApp as e:
517
print(f"Not a valid extension app: {e}")
518
```