Mikko Ohtamaa
Mikko Ohtamaa

Reputation: 83666

Detecting if ipython notebook is outputting to a terminal

I want to detect if a Jupyter Notebook is being executed in a terminal e.g. via ipython --TerminalIPythonApp.file_to_run command, as opposite to a HTML enabled notebook. Note that this is different from detecting if the Python code is run with python or within a notebook.

Based on this I can format Pandas DataFrames suitable either for HTML or for terminal display.

How can I detect if a notebook is outputting to a terminal?

Upvotes: 3

Views: 302

Answers (3)

Robert Grimm
Robert Grimm

Reputation: 21

Unfortunately, the answers so far are similar to the ones on this question, namely trying to divine runtime features from incidentals such as get_ipython() being defined or returning an instance of a certain class. Those tests will always be brittle and probably also wrong because there are many variations of notebook runtimes, such as Jupyter, JupyterLab, and Google Colab, and the tests only provide indirect evidence for HTML support.

Reading Matt's answer (which is the same as the previous link) made me realize that there is a more direct and robust way: Try to display something and see what format the runtime picks.

To turn that idea into code, here's a small helper class. It can be displayed as either HTML or plain text. It also records the runtime's choice.

class DisplayInspector:
    """Objects that display as HTML or text."""
    def __init__(self) -> None:
        self.status = None

    def _repr_html_(self) -> str:
        self.status = 'HTML'
        return '<p>Checking HTML support: ✅</p>'

    def __repr__(self) -> str:
        self.status = 'Plain'
        return 'Checking HTML support: ❌'

We also need a function to actually conduct the experiment:

import sys

def supports_html() -> bool:
    """Test whether current runtime supports HTML."""
    if (
        'IPython' not in sys.modules
        or 'IPython.display' not in sys.modules
    ):
        return False

    from IPython.display import display
    inspector = DisplayInspector()
    display(inspector)
    return inspector.status == 'HTML'

That's it. By design, this test leaves a visible trace. That's a good thing: It makes the test more robust because it actually tests what we want to know. That's also a bad thing: It leaves a visible trace. If that bugs you, I would recommend returning empty strings from both methods. At least, that worked for me.

Upvotes: 2

Mikko Ohtamaa
Mikko Ohtamaa

Reputation: 83666

Based on William's answer I figured this out.

Terminal output example:

enter image description here

HTML output example:

enter image description here

Here is some sample code, cleaned up from William's answer and also some more context how this can be utilised.

See an example quantitative finance notebook using this. See full source code.

"""Helpers to deal with Jupyter Notebook issues."""
import enum
from typing import Callable

import pandas as pd
from IPython import get_ipython
from IPython.display import display
from IPython.terminal.interactiveshell import TerminalInteractiveShell
from ipykernel.zmqshell import ZMQInteractiveShell


class JupyterOutputMode(enum.Enum):
    """What kind of output Jupyter Notebook supports."""

    #: We could not figure out - please debug
    unknown = "unknown"

    #: The notebook is run by terminal
    terminal = "terminal"

    #: Your normal HTML notebook
    html = "html"


def get_notebook_execution_mode() -> JupyterOutputMode:
    """Determine if the Jupyter Notebook supports HTML output."""

    # See https://stackoverflow.com/questions/70768390/detecting-if-ipython-notebook-is-outputting-to-a-terminal
    # for discussion
    ipython = get_ipython()

    if isinstance(ipython, TerminalInteractiveShell):
        # Hello ANSI graphics my old friend
        return JupyterOutputMode.terminal
    elif isinstance(ipython, ZMQInteractiveShell):
        # MAke an assumption ZMQ instance is a HTML notebook
        return JupyterOutputMode.html

    return JupyterOutputMode.unknown


def display_with_styles(df: pd.DataFrame, apply_styles_func: Callable):
    """Display a Pandas dataframe as a table.

    DataFrame styler objects only support HTML output.
    If the Jupyter Notebook output does not have HTML support,
    (it is a command line), then display DataFrame as is
    without styles.

    For `apply_style_func` example see :py:method:`tradingstrategy.analysis.portfolioanalyzer.expand_timeline`.

    :param df: Pandas Dataframe we want to display as a table.

    :param apply_styles_func: A function to call on DataFrame to add its styles on it.
        We need to pass this as callable due to Pandas architectural limitations.
        The function will create styles using `pandas.DataFrame.style` object.
        However if styles are applied the resulting object can no longer be displayed in a terminal.
        Thus, we need to separate the procses of creating dataframe and creating styles and applying them.

    """
    mode = get_notebook_execution_mode()
    if mode == JupyterOutputMode.html:
        display(apply_styles_func(df))
    else:
        display(df)

Upvotes: 1

William
William

Reputation: 486

This might help.

from IPython import get_ipython
def type_of_execution():
    try:
        type_of_exec = str(type(get_ipython()))
        if 'terminal' in type_of_exec:
            return 'terminal'
        elif 'zmqshell' in type_of_exec:
            return 'jupyter'
        else:
            return 'python'
    except:
        return 'terminal likely'
print("Checking..")
print(type_of_execution())

Upvotes: 2

Related Questions