Skip to content

Commit

Permalink
only load enabled extension packages
Browse files Browse the repository at this point in the history
- only load metadata if a given extension package is enabled
- module will be None, extension points will be empty if disabled
- unify use of traitlets, removing redundant use of properties
  • Loading branch information
minrk committed Jan 17, 2023
1 parent 4e9d105 commit 2f8cace
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 34 deletions.
65 changes: 31 additions & 34 deletions jupyter_server/extension/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import importlib

from tornado.gen import multi
from traitlets import Any, Bool, Dict, HasTraits, Instance, Unicode, default, observe
from traitlets import Any, Bool, Dict, HasTraits, Instance, List, Unicode, default, observe
from traitlets import validate as validate_trait
from traitlets.config import LoggingConfigurable

Expand Down Expand Up @@ -157,54 +157,51 @@ class ExtensionPackage(LoggingConfigurable):
"""

name = Unicode(help="Name of the an importable Python package.")
enabled = Bool(False).tag(config=True)
enabled = Bool(False, help="Whether the extension package is enabled.")

def __init__(self, *args, **kwargs):
_linked_points = Dict()
extension_points = Dict()
module = Any(allow_none=True, help="The module for this extension package. None if not enabled")
metadata = List(Dict(), help="Extension metadata loaded from the extension package.")
version = Unicode(
help="""
The version of this extension package, if it can be found.
Otherwise, an empty string.
""",
)

@default("version")
def _load_version(self):
if not self.enabled:
return ""
return getattr(self.module, "__version__", "")

def __init__(self, **kwargs):
"""Initialize an extension package."""
# Store extension points that have been linked.
self._linked_points = {}
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
if self.enabled:
self._load_metadata()

_linked_points: dict = {}
def _load_metadata(self):
"""Import package and load metadata
@validate_trait("name")
def _validate_name(self, proposed):
name = proposed["value"]
self._extension_points = {}
Only used if extension package is enabled
"""
name = self.name
try:
self._module, self._metadata = get_metadata(name, self.log)
self.module, self.metadata = get_metadata(name, logger=self.log)
except ImportError as e:
msg = (
f"The module '{name}' could not be found ({e}). Are you "
"sure the extension is installed?"
)
raise ExtensionModuleNotFound(msg) from None
# Create extension point interfaces for each extension path.
for m in self._metadata:
for m in self.metadata:
point = ExtensionPoint(metadata=m)
self._extension_points[point.name] = point
self.extension_points[point.name] = point
return name

@property
def module(self):
"""Extension metadata loaded from the extension package."""
return self._module

@property
def version(self) -> str:
"""Get the version of this package, if it's given. Otherwise, return an empty string"""
return getattr(self._module, "__version__", "")

@property
def metadata(self):
"""Extension metadata loaded from the extension package."""
return self._metadata

@property
def extension_points(self):
"""A dictionary of extension points."""
return self._extension_points

def validate(self):
"""Validate all extension points in this package."""
for extension in self.extension_points.values():
Expand Down
21 changes: 21 additions & 0 deletions tests/extension/test_manager.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import sys
import unittest.mock as mock

import pytest
Expand Down Expand Up @@ -144,3 +145,23 @@ def test_extension_manager_fail_load(jp_serverapp, has_app):
manager.load_extension(name)
else:
manager.load_extension(name)


@pytest.mark.parametrize("has_app", [True, False])
def test_disable_no_import(jp_serverapp, has_app):
# de-import modules so we can detect if they are re-imported
disabled_ext = "tests.extension.mockextensions.mock1"
enabled_ext = "tests.extension.mockextensions.mock2"
sys.modules.pop(disabled_ext, None)
sys.modules.pop(enabled_ext, None)

manager = ExtensionManager(serverapp=jp_serverapp if has_app else None)
manager.add_extension(disabled_ext, enabled=False)
manager.add_extension(enabled_ext, enabled=True)
assert disabled_ext not in sys.modules
assert enabled_ext in sys.modules

ext_pkg = manager.extensions[disabled_ext]
assert ext_pkg.extension_points == {}
assert ext_pkg.version == ""
assert ext_pkg.metadata == []

0 comments on commit 2f8cace

Please sign in to comment.