diff --git a/src/machinevisiontoolbox/base/imageio.py b/src/machinevisiontoolbox/base/imageio.py index 4e45e154..84838ac2 100644 --- a/src/machinevisiontoolbox/base/imageio.py +++ b/src/machinevisiontoolbox/base/imageio.py @@ -1053,17 +1053,23 @@ def _isnotebook() -> bool: """ Determine if code is being run from a Jupyter notebook - ``_isnotebook`` is True if running Jupyter notebook, else False + ``_isnotebook`` is True if running Jupyter notebook, JupyterLite, Google Colab, + or similar, else False :references: - https://stackoverflow.com/questions/15411967/how-can-i-check-if-code- is-executed-in-the-ipython-notebook/39662359#39662359 """ + # JupyterLite runs in the browser, and Colab's shell is google.colab._shell.Shell + # rather than a ZMQInteractiveShell; neither is recognised by the shell test below + if sys.platform == "emscripten" or "google.colab" in sys.modules: + return True + try: shell = get_ipython().__class__.__name__ # type: ignore[name-defined] - if shell in ("Shell", "ZMQInteractiveShell"): - return True # Jupyter notebook or qtconsole or CoLab + if shell == "ZMQInteractiveShell": + return True # Jupyter notebook or qtconsole elif shell == "TerminalInteractiveShell": return False # Terminal running IPython else: diff --git a/tests/test_isnotebook.py b/tests/test_isnotebook.py new file mode 100644 index 00000000..b9698b24 --- /dev/null +++ b/tests/test_isnotebook.py @@ -0,0 +1,62 @@ +"""``_isnotebook`` decides how ``Camera`` reuses its image plane, so it has to +recognise every environment a notebook runs in. These use a fake ``get_ipython`` and +patched ``sys.modules`` / ``sys.platform``: no real Jupyter, Colab or JupyterLite +kernel is exercised.""" + +import builtins +import sys +import types + +import pytest + +from machinevisiontoolbox.base.imageio import _isnotebook + + +@pytest.fixture +def plain(monkeypatch): + """Standard interpreter: no IPython, not Colab, not Pyodide""" + monkeypatch.delattr(builtins, "get_ipython", raising=False) + monkeypatch.delitem(sys.modules, "google.colab", raising=False) + monkeypatch.setattr(sys, "platform", "linux") + return monkeypatch + + +def _shell(monkeypatch, name): + shell = type(name, (), {})() + monkeypatch.setattr(builtins, "get_ipython", lambda: shell, raising=False) + + +def test_a_standard_interpreter_is_not_a_notebook(plain): + assert _isnotebook() is False + + +def test_a_jupyter_kernel_is_a_notebook(plain): + _shell(plain, "ZMQInteractiveShell") + + assert _isnotebook() is True + + +def test_ipython_in_a_terminal_is_not_a_notebook(plain): + _shell(plain, "TerminalInteractiveShell") + + assert _isnotebook() is False + + +def test_colab_is_a_notebook(plain): + plain.setitem(sys.modules, "google.colab", types.ModuleType("google.colab")) + _shell(plain, "Shell") + + assert _isnotebook() is True + + +def test_jupyterlite_is_a_notebook_whatever_its_shell_is_called(plain): + plain.setattr(sys, "platform", "emscripten") + _shell(plain, "SomeOtherShell") + + assert _isnotebook() is True + + +def test_an_unknown_shell_is_not_a_notebook(plain): + _shell(plain, "Shell") # not Colab: google.colab is not loaded + + assert _isnotebook() is False