Skip to content

Commit

Permalink
Merge pull request jupyter-server#502 from minrk/default-config-manager
Browse files Browse the repository at this point in the history
ExtensionManager: load default config manager by default
  • Loading branch information
Zsailer authored May 3, 2021
2 parents 83ee55e + 7528643 commit 31e2de4
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 38 deletions.
87 changes: 49 additions & 38 deletions jupyter_server/extension/manager.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import importlib

from traitlets.config import LoggingConfigurable, Config
from traitlets.config import LoggingConfigurable

from traitlets import (
HasTraits,
Dict,
Unicode,
Bool,
Any,
validate
Instance,
default,
observe,
validate,
)

from .config import ExtensionConfigManager
from .utils import (
ExtensionMetadataError,
ExtensionModuleNotFound,
Expand Down Expand Up @@ -240,35 +244,44 @@ class ExtensionManager(LoggingConfigurable):
linking, loading, and managing Jupyter Server extensions.
Usage:
m = ExtensionManager(jpserver_extensions=extensions)
m = ExtensionManager(config_manager=...)
"""
def __init__(self, config_manager=None, *args, **kwargs):
super().__init__(*args, **kwargs)
# The `enabled_extensions` attribute provides a dictionary
# with extension (package) names mapped to their ExtensionPackage interface
# (see above). This manager simplifies the interaction between the
# ServerApp and the extensions being appended.
self._extensions = {}
# The `_linked_extensions` attribute tracks when each extension
# has been successfully linked to a ServerApp. This helps prevent
# extensions from being re-linked recursively unintentionally if another
# extension attempts to link extensions again.
self._linked_extensions = {}
self._config_manager = config_manager
if self._config_manager:
self.from_config_manager(self._config_manager)

@property
def config_manager(self):
return self._config_manager
config_manager = Instance(ExtensionConfigManager, allow_none=True)

@default("config_manager")
def _load_default_config_manager(self):
config_manager = ExtensionConfigManager()
self._load_config_manager(config_manager)
return config_manager

@observe("config_manager")
def _config_manager_changed(self, change):
if change.new:
self._load_config_manager(change.new)

# The `extensions` attribute provides a dictionary
# with extension (package) names mapped to their ExtensionPackage interface
# (see above). This manager simplifies the interaction between the
# ServerApp and the extensions being appended.
extensions = Dict(
help="""
Dictionary with extension package names as keys
and ExtensionPackage objects as values.
"""
)

@property
def extensions(self):
"""Dictionary with extension package names as keys
and an ExtensionPackage objects as values.
# The `_linked_extensions` attribute tracks when each extension
# has been successfully linked to a ServerApp. This helps prevent
# extensions from being re-linked recursively unintentionally if another
# extension attempts to link extensions again.
linked_extensions = Dict(
help="""
Dictionary with extension names as keys
values are True if the extension is linked, False if not.
"""
# Sort enabled extensions before
return self._extensions
)

@property
def extension_points(self):
Expand All @@ -279,16 +292,14 @@ def extension_points(self):
for name, point in value.extension_points.items()
}

@property
def linked_extensions(self):
"""Dictionary with extension names as keys; values are
True if the extension is linked, False if not."""
return self._linked_extensions

def from_config_manager(self, config_manager):
"""Add extensions found by an ExtensionConfigManager"""
self._config_manager = config_manager
jpserver_extensions = self._config_manager.get_jpserver_extensions()
# load triggered via config_manager trait observer
self.config_manager = config_manager

def _load_config_manager(self, config_manager):
"""Actually load our config manager"""
jpserver_extensions = config_manager.get_jpserver_extensions()
self.from_jpserver_extensions(jpserver_extensions)

def from_jpserver_extensions(self, jpserver_extensions):
Expand All @@ -302,21 +313,21 @@ def add_extension(self, extension_name, enabled=False):
"""
try:
extpkg = ExtensionPackage(name=extension_name, enabled=enabled)
self._extensions[extension_name] = extpkg
self.extensions[extension_name] = extpkg
return True
# Raise a warning if the extension cannot be loaded.
except Exception as e:
self.log.warning(e)
return False

def link_extension(self, name, serverapp):
linked = self._linked_extensions.get(name, False)
linked = self.linked_extensions.get(name, False)
extension = self.extensions[name]
if not linked and extension.enabled:
try:
# Link extension and store links
extension.link_all_points(serverapp)
self._linked_extensions[name] = True
self.linked_extensions[name] = True
self.log.info("{name} | extension was successfully linked.".format(name=name))
except Exception as e:
self.log.warning(e)
Expand Down
11 changes: 11 additions & 0 deletions jupyter_server/tests/extension/test_manager.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import os

import pytest

from jupyter_core.paths import jupyter_config_path

from jupyter_server.extension.manager import (
ExtensionPoint,
ExtensionPackage,
Expand Down Expand Up @@ -68,11 +73,17 @@ def test_extension_package_notfound_error():
ExtensionPackage(name="nonexistent")


def _normalize_path(path_list):
return [p.rstrip(os.path.sep) for p in path_list]


def test_extension_manager_api():
jpserver_extensions = {
"jupyter_server.tests.extension.mockextensions": True
}
manager = ExtensionManager()
assert manager.config_manager
assert _normalize_path(manager.config_manager.read_config_path) == _normalize_path(jupyter_config_path())
manager.from_jpserver_extensions(jpserver_extensions)
assert len(manager.extensions) == 1
assert "jupyter_server.tests.extension.mockextensions" in manager.extensions
Expand Down

0 comments on commit 31e2de4

Please sign in to comment.