From 304a9f127ce4ea15b3b6179d488672a3d3965467 Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Sun, 1 Nov 2026 16:58:04 +1000 Subject: [PATCH] fix: _isnotebook() recognises JupyterLite, and Colab by its module It matched a shell class named Shell or ZMQInteractiveShell and had no JupyterLite check, so Camera took its desktop image-plane branch in the browser if the Pyodide shell has another name. Test for emscripten and for google.colab first, as RTB does and as spatialmath's environment() does (rai-opensource/spatialmath-python#271). Closes #166 Co-Authored-By: Claude Sonnet 5.5 --- src/machinevisiontoolbox/base/imageio.py | 12 +++-- tests/test_isnotebook.py | 62 ++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 tests/test_isnotebook.py 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