Skip to content

Commit d643985

Browse files
committed
only load enabled extension packages
- 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
1 parent 4e9d105 commit d643985

File tree

2 files changed

+54
-36
lines changed

2 files changed

+54
-36
lines changed

jupyter_server/extension/manager.py

+31-34
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import importlib
33

44
from tornado.gen import multi
5-
from traitlets import Any, Bool, Dict, HasTraits, Instance, Unicode, default, observe
5+
from traitlets import Any, Bool, Dict, HasTraits, Instance, List, Unicode, default, observe
66
from traitlets import validate as validate_trait
77
from traitlets.config import LoggingConfigurable
88

@@ -157,54 +157,51 @@ class ExtensionPackage(LoggingConfigurable):
157157
"""
158158

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

162-
def __init__(self, *args, **kwargs):
162+
_linked_points = Dict()
163+
extension_points = Dict()
164+
module = Any(allow_none=True, help="The module for this extension package. None if not enabled")
165+
metadata = List(Dict(), help="Extension metadata loaded from the extension package.")
166+
version = Unicode(
167+
help="""
168+
The version of this extension package, if it can be found.
169+
Otherwise, an empty string.
170+
""",
171+
)
172+
173+
@default("version")
174+
def _load_version(self):
175+
if not self.enabled:
176+
return ""
177+
return getattr(self.module, "__version__", "")
178+
179+
def __init__(self, **kwargs):
163180
"""Initialize an extension package."""
164-
# Store extension points that have been linked.
165-
self._linked_points = {}
166-
super().__init__(*args, **kwargs)
181+
super().__init__(**kwargs)
182+
if self.enabled:
183+
self._load_metadata()
167184

168-
_linked_points: dict = {}
185+
def _load_metadata(self):
186+
"""Import package and load metadata
169187
170-
@validate_trait("name")
171-
def _validate_name(self, proposed):
172-
name = proposed["value"]
173-
self._extension_points = {}
188+
Only used if extension package is enabled
189+
"""
190+
name = self.name
174191
try:
175-
self._module, self._metadata = get_metadata(name, self.log)
192+
self.module, self.metadata = get_metadata(name, logger=self.log)
176193
except ImportError as e:
177194
msg = (
178195
f"The module '{name}' could not be found ({e}). Are you "
179196
"sure the extension is installed?"
180197
)
181198
raise ExtensionModuleNotFound(msg) from None
182199
# Create extension point interfaces for each extension path.
183-
for m in self._metadata:
200+
for m in self.metadata:
184201
point = ExtensionPoint(metadata=m)
185-
self._extension_points[point.name] = point
202+
self.extension_points[point.name] = point
186203
return name
187204

188-
@property
189-
def module(self):
190-
"""Extension metadata loaded from the extension package."""
191-
return self._module
192-
193-
@property
194-
def version(self) -> str:
195-
"""Get the version of this package, if it's given. Otherwise, return an empty string"""
196-
return getattr(self._module, "__version__", "")
197-
198-
@property
199-
def metadata(self):
200-
"""Extension metadata loaded from the extension package."""
201-
return self._metadata
202-
203-
@property
204-
def extension_points(self):
205-
"""A dictionary of extension points."""
206-
return self._extension_points
207-
208205
def validate(self):
209206
"""Validate all extension points in this package."""
210207
for extension in self.extension_points.values():

tests/extension/test_manager.py

+23-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import sys
23
import unittest.mock as mock
34

45
import pytest
@@ -60,7 +61,7 @@ def test_extension_package_api():
6061
path1 = metadata_list[0]
6162
app = path1["app"]
6263

63-
e = ExtensionPackage(name="tests.extension.mockextensions")
64+
e = ExtensionPackage(name="tests.extension.mockextensions", enabled=True)
6465
e.extension_points
6566
assert hasattr(e, "extension_points")
6667
assert len(e.extension_points) == len(metadata_list)
@@ -70,7 +71,7 @@ def test_extension_package_api():
7071

7172
def test_extension_package_notfound_error():
7273
with pytest.raises(ExtensionModuleNotFound):
73-
ExtensionPackage(name="nonexistent")
74+
ExtensionPackage(name="nonexistent", enabled=True)
7475

7576

7677
def _normalize_path(path_list):
@@ -144,3 +145,23 @@ def test_extension_manager_fail_load(jp_serverapp, has_app):
144145
manager.load_extension(name)
145146
else:
146147
manager.load_extension(name)
148+
149+
150+
@pytest.mark.parametrize("has_app", [True, False])
151+
def test_disable_no_import(jp_serverapp, has_app):
152+
# de-import modules so we can detect if they are re-imported
153+
disabled_ext = "tests.extension.mockextensions.mock1"
154+
enabled_ext = "tests.extension.mockextensions.mock2"
155+
sys.modules.pop(disabled_ext, None)
156+
sys.modules.pop(enabled_ext, None)
157+
158+
manager = ExtensionManager(serverapp=jp_serverapp if has_app else None)
159+
manager.add_extension(disabled_ext, enabled=False)
160+
manager.add_extension(enabled_ext, enabled=True)
161+
assert disabled_ext not in sys.modules
162+
assert enabled_ext in sys.modules
163+
164+
ext_pkg = manager.extensions[disabled_ext]
165+
assert ext_pkg.extension_points == {}
166+
assert ext_pkg.version == ""
167+
assert ext_pkg.metadata == []

0 commit comments

Comments
 (0)