diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 2dbbcca0f..88be0fba9 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -933,7 +933,8 @@ class NotebookApp(JupyterApp): nbserver_extensions = Dict({}, config=True, help=("Dict of Python modules to load as notebook server extensions." "Entry values can be used to enable and disable the loading of" - "the extensions.") + "the extensions. The extensions will be loaded in alphabetical " + "order.") ) reraise_server_extension_failures = Bool( @@ -1193,7 +1194,7 @@ class NotebookApp(JupyterApp): if not modulename in self.nbserver_extensions: self.nbserver_extensions[modulename] = True - for modulename in self.nbserver_extensions: + for modulename in sorted(self.nbserver_extensions): if self.nbserver_extensions[modulename]: try: mod = importlib.import_module(modulename) diff --git a/notebook/tests/test_serverextensions.py b/notebook/tests/test_serverextensions.py index 4c5a68c08..b24754dda 100644 --- a/notebook/tests/test_serverextensions.py +++ b/notebook/tests/test_serverextensions.py @@ -1,4 +1,5 @@ import os +import sys from unittest import TestCase try: from unittest.mock import patch @@ -13,8 +14,17 @@ from traitlets.tests.utils import check_help_all_output from notebook.serverextensions import toggle_serverextension_python from notebook import nbextensions +from notebook.notebookapp import NotebookApp from notebook.nbextensions import _get_config_dir +if sys.version_info > (3,): + from types import SimpleNamespace +else: + class SimpleNamespace(object): + pass + +from collections import OrderedDict + def test_help_output(): check_help_all_output('notebook.serverextensions') @@ -87,3 +97,43 @@ class TestInstallServerExtension(TestCase): config = self._get_config() assert not config['mockextension'] + + +class TestOrderedServerExtension(TestCase): + """ + Test that Server Extensions are loaded _in order_ + """ + + def setUp(self): + mockextension1 = SimpleNamespace() + mockextension2 = SimpleNamespace() + + def load_jupyter_server_extension(obj): + obj.mockI = True + obj.mock_shared = 'I' + + mockextension1.load_jupyter_server_extension = load_jupyter_server_extension + + def load_jupyter_server_extension(obj): + obj.mockII = True + obj.mock_shared = 'II' + + mockextension2.load_jupyter_server_extension = load_jupyter_server_extension + + sys.modules['mockextension2'] = mockextension2 + sys.modules['mockextension1'] = mockextension1 + + def tearDown(self): + del sys.modules['mockextension2'] + del sys.modules['mockextension1'] + + + def test_load_ordered(self): + app = NotebookApp() + app.nbserver_extensions = OrderedDict([('mockextension2',True),('mockextension1',True)]) + + app.init_server_extensions() + + assert app.mockII is True, "Mock II should have been loaded" + assert app.mockI is True, "Mock I should have been loaded" + assert app.mock_shared == 'II', "Mock II should be loaded after Mock I"