Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExtensionManager: load default config manager by default #502

Merged
merged 1 commit into from
May 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -238,35 +242,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 @@ -277,16 +290,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 @@ -300,21 +311,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 @@ -66,11 +71,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