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

Respect reraise setting #571

Merged
merged 7 commits into from
Aug 24, 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
9 changes: 5 additions & 4 deletions .github/workflows/python-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ jobs:
fail-fast: false
matrix:
os: [macos]
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10-dev", "pypy3"]
python-version:
["3.6", "3.7", "3.8", "3.9", "3.10-dev", "pypy-3.7-v7.3.3"]
steps:
- name: Checkout
uses: actions/checkout@v2
Expand Down Expand Up @@ -43,11 +44,11 @@ jobs:
pip freeze
pip check
- name: Run the tests
if: ${{ matrix.python-version != 'pypy3' }}
if: ${{ !startsWith( matrix.python-version, 'pypy' ) }}
run: |
pytest -vv --cov jupyter_server --cov-branch --cov-report term-missing:skip-covered
- name: Run the tests on pypy
if: ${{ matrix.python-version == 'pypy3' }}
if: ${{ startsWith( matrix.python-version, 'pypy' ) }}
run: |
pytest -vv
- name: Install the Python dependencies for the examples
Expand All @@ -57,6 +58,6 @@ jobs:
run: |
pytest examples/simple/tests/test_handlers.py --confcutdir=$PWD
- name: Coverage
if: ${{ matrix.python-version != 'pypy3' }}
if: ${{ !startsWith( matrix.python-version, 'pypy' ) }}
run: |
codecov
26 changes: 17 additions & 9 deletions jupyter_server/extension/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ class ExtensionManager(LoggingConfigurable):

config_manager = Instance(ExtensionConfigManager, allow_none=True)

serverapp = Any() # Use Any to avoid circular import of Instance(ServerApp)

@default("config_manager")
def _load_default_config_manager(self):
config_manager = ExtensionConfigManager()
Expand Down Expand Up @@ -327,28 +329,34 @@ def add_extension(self, extension_name, enabled=False):
return True
# Raise a warning if the extension cannot be loaded.
except Exception as e:
if self.serverapp.reraise_server_extension_failures:
raise
self.log.warning(e)
return False

def link_extension(self, name, serverapp):
def link_extension(self, name):
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)
extension.link_all_points(self.serverapp)
self.linked_extensions[name] = True
self.log.info("{name} | extension was successfully linked.".format(name=name))
except Exception as e:
if self.serverapp.reraise_server_extension_failures:
raise
self.log.warning(e)

def load_extension(self, name, serverapp):
def load_extension(self, name):
extension = self.extensions.get(name)

if extension.enabled:
try:
points = extension.load_all_points(serverapp)
extension.load_all_points(self.serverapp)
except Exception as e:
if self.serverapp.reraise_server_extension_failures:
raise
self.log.debug("".join(traceback.format_exception(*sys.exc_info())))
self.log.warning(
"{name} | extension failed loading with message: {error}".format(
Expand All @@ -365,25 +373,25 @@ async def stop_extension(self, name, apps):
await app.stop_extension()
self.log.debug('{} | extension app "{}" stopped'.format(name, app.name))

def link_all_extensions(self, serverapp):
def link_all_extensions(self):
"""Link all enabled extensions
to an instance of ServerApp
"""
# Sort the extension names to enforce deterministic linking
# order.
for name in self.sorted_extensions.keys():
self.link_extension(name, serverapp)
self.link_extension(name)

def load_all_extensions(self, serverapp):
def load_all_extensions(self):
"""Load all enabled extensions and append them to
the parent ServerApp.
"""
# Sort the extension names to enforce deterministic loading
# order.
for name in self.sorted_extensions.keys():
self.load_extension(name, serverapp)
self.load_extension(name)

async def stop_all_extensions(self, serverapp):
async def stop_all_extensions(self):
"""Call the shutdown hooks in all extensions."""
await multi(
[
Expand Down
8 changes: 4 additions & 4 deletions jupyter_server/serverapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2076,9 +2076,9 @@ def init_server_extensions(self):
and load its own config.
"""
# Create an instance of the ExtensionManager.
self.extension_manager = ExtensionManager(log=self.log)
self.extension_manager = ExtensionManager(log=self.log, serverapp=self)
self.extension_manager.from_jpserver_extensions(self.jpserver_extensions)
self.extension_manager.link_all_extensions(self)
self.extension_manager.link_all_extensions()

def load_server_extensions(self):
"""Load any extensions specified by config.
Expand All @@ -2088,7 +2088,7 @@ def load_server_extensions(self):

The extension API is experimental, and may change in future releases.
"""
self.extension_manager.load_all_extensions(self)
self.extension_manager.load_all_extensions()

def init_mime_overrides(self):
# On some Windows machines, an application has registered incorrect
Expand Down Expand Up @@ -2365,7 +2365,7 @@ async def cleanup_extensions(self):
"Shutting down %d extension", "Shutting down %d extensions", n_extensions
)
self.log.info(extension_msg % n_extensions)
await run_sync_in_loop(self.extension_manager.stop_all_extensions(self))
await run_sync_in_loop(self.extension_manager.stop_all_extensions())

def running_server_info(self, kernel_count=True):
"Return the current working directory and the server url information"
Expand Down
2 changes: 1 addition & 1 deletion jupyter_server/tests/extension/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def test_stop_extension(jp_serverapp, caplog):
calls = 0

# load extensions (make sure we only have the one extension loaded
jp_serverapp.extension_manager.load_all_extensions(jp_serverapp)
jp_serverapp.extension_manager.load_all_extensions()
extension_name = "jupyter_server.tests.extension.mockextensions"
assert list(jp_serverapp.extension_manager.extension_apps) == [extension_name]

Expand Down
47 changes: 43 additions & 4 deletions jupyter_server/tests/extension/test_manager.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import unittest.mock as mock

import pytest
from jupyter_core.paths import jupyter_config_path
Expand Down Expand Up @@ -74,9 +75,9 @@ def _normalize_path(path_list):
return [p.rstrip(os.path.sep) for p in path_list]


def test_extension_manager_api():
def test_extension_manager_api(jp_serverapp):
jpserver_extensions = {"jupyter_server.tests.extension.mockextensions": True}
manager = ExtensionManager()
manager = ExtensionManager(serverapp=jp_serverapp)
assert manager.config_manager
expected = _normalize_path(os.path.join(jupyter_config_path()[0], "serverconfig"))
assert _normalize_path(manager.config_manager.read_config_path[0]) == expected
Expand All @@ -87,7 +88,45 @@ def test_extension_manager_api():

def test_extension_manager_linked_extensions(jp_serverapp):
name = "jupyter_server.tests.extension.mockextensions"
manager = ExtensionManager()
manager = ExtensionManager(serverapp=jp_serverapp)
manager.add_extension(name, enabled=True)
manager.link_extension(name, jp_serverapp)
manager.link_extension(name)
assert name in manager.linked_extensions


def test_extension_manager_fail_add(jp_serverapp):
name = "jupyter_server.tests.extension.notanextension"
manager = ExtensionManager(serverapp=jp_serverapp)
manager.add_extension(name, enabled=True) # should only warn
jp_serverapp.reraise_server_extension_failures = True
with pytest.raises(ExtensionModuleNotFound):
manager.add_extension(name, enabled=True)


def test_extension_manager_fail_link(jp_serverapp):
name = "jupyter_server.tests.extension.mockextensions.app"
with mock.patch(
"jupyter_server.tests.extension.mockextensions.app.MockExtensionApp.parse_command_line",
side_effect=RuntimeError,
):
manager = ExtensionManager(serverapp=jp_serverapp)
manager.add_extension(name, enabled=True)
manager.link_extension(name) # should only warn
jp_serverapp.reraise_server_extension_failures = True
with pytest.raises(RuntimeError):
manager.link_extension(name)


def test_extension_manager_fail_load(jp_serverapp):
name = "jupyter_server.tests.extension.mockextensions.app"
with mock.patch(
"jupyter_server.tests.extension.mockextensions.app.MockExtensionApp.initialize_handlers",
side_effect=RuntimeError,
):
manager = ExtensionManager(serverapp=jp_serverapp)
manager.add_extension(name, enabled=True)
manager.link_extension(name)
manager.load_extension(name) # should only warn
jp_serverapp.reraise_server_extension_failures = True
with pytest.raises(RuntimeError):
manager.load_extension(name)
2 changes: 1 addition & 1 deletion jupyter_server/tests/services/kernels/test_cull.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def jp_argv(request):
]


CULL_TIMEOUT = 10 if platform.python_implementation() == "PyPy" else 5
CULL_TIMEOUT = 30 if platform.python_implementation() == "PyPy" else 5
CULL_INTERVAL = 1


Expand Down
6 changes: 3 additions & 3 deletions jupyter_server/tests/test_terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def terminal_path(tmp_path):
shutil.rmtree(str(subdir), ignore_errors=True)


CULL_TIMEOUT = 2
CULL_TIMEOUT = 10
CULL_INTERVAL = 3


Expand Down Expand Up @@ -128,7 +128,7 @@ async def test_terminal_create_with_cwd(jp_fetch, jp_ws_fetch, terminal_path):
message_stdout = ""
while True:
try:
message = await asyncio.wait_for(ws.read_message(), timeout=1.0)
message = await asyncio.wait_for(ws.read_message(), timeout=5.0)
except asyncio.TimeoutError:
break

Expand Down Expand Up @@ -164,7 +164,7 @@ async def test_culling(jp_server_config, jp_fetch):
last_activity = term["last_activity"]

culled = False
for i in range(10): # Culling should occur in a few seconds
for i in range(CULL_TIMEOUT + CULL_INTERVAL):
try:
resp = await jp_fetch(
"api",
Expand Down