Skip to content
Open
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
3 changes: 2 additions & 1 deletion spatialmath/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from spatialmath.base.vectors import * # lgtm [py/polluting-import]
from spatialmath.base.symbolic import * # lgtm [py/polluting-import]
from spatialmath.base.numeric import * # lgtm [py/polluting-import]
from spatialmath.base._environment import * # lgtm [py/polluting-import]

import importlib

Expand Down Expand Up @@ -45,7 +46,6 @@
"plotvol2": "graphics",
"plotvol3": "graphics",
"expand_dims": "graphics",
"isnotebook": "graphics",
}


Expand Down Expand Up @@ -392,6 +392,7 @@ def __getattr__(name):
"plot_cuboid",
"axes_logic",
"expand_dims",
"environment",
"isnotebook",
# spatial.base.numeric
"numjac",
Expand Down
78 changes: 78 additions & 0 deletions spatialmath/base/_environment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Part of Spatial Math Toolbox for Python
# Copyright (c) 2000 Peter Corke
# MIT Licence, see details in top-level file: LICENCE

"""
Detect the interactive environment that Python is running in.

This module deliberately does not import Matplotlib, so it is cheap to import
and works when Matplotlib is not installed.
"""

import sys
from typing import Literal

__all__ = ["environment", "isnotebook"]

Environment = Literal["terminal", "ipython", "jupyter", "colab", "jupyterlite"]


def environment() -> Environment:
"""
Determine the environment that Python is running in

:return: the environment
:rtype: str

The result is one of:

======================== ====================================================
``"terminal"`` standard Python interpreter, or script
``"ipython"`` IPython running in a terminal
``"jupyter"`` Jupyter kernel, eg. JupyterLab, Notebook, VS Code or
qtconsole
``"colab"`` Google Colab
``"jupyterlite"`` JupyterLite, running in the browser (Pyodide)
======================== ====================================================

.. note:: The different Jupyter front ends (JupyterLab, classic Notebook,
VS Code, Jupyter Desktop) cannot be reliably told apart from within the
kernel, so all are reported as ``"jupyter"``. How figures will be
rendered depends on the Matplotlib backend, not on the front end.

:seealso: :func:`isnotebook`
"""
# Colab and JupyterLite both also provide an IPython kernel, so test for
# them first
if "google.colab" in sys.modules:
return "colab"
if sys.platform == "emscripten":
return "jupyterlite"

try:
shell = get_ipython().__class__.__name__ # type: ignore[name-defined]
except NameError:
return "terminal" # probably standard Python interpreter

if shell == "ZMQInteractiveShell":
return "jupyter" # Jupyter notebook or qtconsole
elif shell == "TerminalInteractiveShell":
return "ipython"
else:
return "terminal" # other type (?)


def isnotebook() -> bool:
"""
Determine if code is being run from a Jupyter notebook

:return: True if running in a Jupyter kernel, Google Colab or JupyterLite
:rtype: bool

:seealso: :func:`environment`

:references:

- https://stackoverflow.com/questions/15411967/how-can-i-check-if-code-is-executed-in-the-ipython-notebook/39662359#39662359
"""
return environment() in ("jupyter", "colab", "jupyterlite")
20 changes: 0 additions & 20 deletions spatialmath/base/graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -1783,26 +1783,6 @@ def expand_dims(dim: ArrayLike = None, nd: int = 2) -> NDArray:
else:
raise ValueError("nd is 2 or 3")

def isnotebook() -> bool:
"""
Determine if code is being run from a Jupyter notebook

:references:

- https://stackoverflow.com/questions/15411967/how-can-i-check-if-code-
is-executed-in-the-ipython-notebook/39662359#39662359
"""
try:
shell = get_ipython().__class__.__name__
if shell == "ZMQInteractiveShell":
return True # Jupyter notebook or qtconsole
elif shell == "TerminalInteractiveShell":
return False # Terminal running IPython
else:
return False # Other type (?)
except NameError:
return False # Probably standard Python interpreter

if __name__ == "__main__":
import pathlib

Expand Down
64 changes: 64 additions & 0 deletions tests/base/test_environment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import builtins
import sys

import pytest

from spatialmath.base import environment, isnotebook

# These tests are mocked: a fake get_ipython() and sys.modules / sys.platform
# stand in for the real front ends, which cannot be run under CI.


def fake_shell(name):
shell = type(name, (), {})()
return lambda: shell


@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 test_terminal(plain):
assert environment() == "terminal"
assert not isnotebook()


def test_ipython_terminal(plain):
plain.setattr(
builtins, "get_ipython", fake_shell("TerminalInteractiveShell"), raising=False
)
assert environment() == "ipython"
assert not isnotebook()


def test_jupyter(plain):
plain.setattr(
builtins, "get_ipython", fake_shell("ZMQInteractiveShell"), raising=False
)
assert environment() == "jupyter"
assert isnotebook()


def test_unknown_shell(plain):
plain.setattr(builtins, "get_ipython", fake_shell("SomethingElse"), raising=False)
assert environment() == "terminal"
assert not isnotebook()


def test_colab(plain):
# Colab's shell is not a ZMQInteractiveShell, so detect it by the module
plain.setattr(builtins, "get_ipython", fake_shell("Shell"), raising=False)
plain.setitem(sys.modules, "google.colab", object())
assert environment() == "colab"
assert isnotebook()


def test_jupyterlite(plain):
plain.setattr(sys, "platform", "emscripten")
assert environment() == "jupyterlite"
assert isnotebook()
Loading