jupyter-widgets / ipywidgets

Interactive Widgets for the Jupyter Notebook
https://ipywidgets.readthedocs.io
BSD 3-Clause "New" or "Revised" License
3.16k stars 950 forks source link

ipywidgets.Output() context suppresses exceptions #3208

Open yumasheta opened 3 years ago

yumasheta commented 3 years ago

ipywidgets.Output() context manager supresses exceptions and prevents correct exit code

Description

For example take this code snippet and save it to a cell of a *.ipynb notebook.

import ipywidgets
o = ipywidgets.Output()
with o:
    raise Exception()
display(o)
print("execution continues")

Then execute the notebook with ipython:

$ ipython path/to/notebook.ipynb

The output will be

---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
/tmp/ipywidgets_bug/example.ipynb in <module>
      2 o = ipywidgets.Output()
      3 with o:
----> 4     raise Exception()
      5 display(o)
      6 print("execution continues")

Exception: 
Output()
execution continues

Expected behavior

The call to ipython should fail. If it is not aborted at the time when the exception is raised, at least it should exit with exit code other than 0.

Actual Behavior

The exception raised in the output context is suppressed. Execution continues and the ipython call exits with code 0.

Also the stderr output from the exception is not displayed unless display(o) is called explicitly. But this I reckon is expected behavior.

Context

Troubleshoot Output
$PATH:
    /tmp/ipywidgets_bug/.venv/bin
    /usr/local/bin
    /usr/bin
    /usr/local/sbin
    /opt/cuda/bin
    /opt/cuda/nsight_compute
    /opt/cuda/nsight_systems/bin
    /usr/lib/jvm/default/bin
    /usr/bin/site_perl
    /usr/bin/vendor_perl
    /usr/bin/core_perl

sys.path:
    /tmp/ipywidgets_bug/.venv/bin
    /usr/lib/python39.zip
    /usr/lib/python3.9
    /usr/lib/python3.9/lib-dynload
    /tmp/ipywidgets_bug/.venv/lib/python3.9/site-packages

sys.executable:
    /tmp/ipywidgets_bug/.venv/bin/python

sys.version:
    3.9.5 (default, May 24 2021, 12:50:35) 
    [GCC 11.1.0]

platform.platform():
    Linux-5.12.7-arch1-1-x86_64-with-glibc2.33

which -a jupyter:
    /tmp/ipywidgets_bug/.venv/bin/jupyter
    /usr/bin/jupyter

pip list:
    Package            Version
    ------------------ -------
    attrs              21.2.0
    backcall           0.2.0
    decorator          5.0.9
    ipykernel          5.5.5
    ipython            7.24.1
    ipython-genutils   0.2.0
    ipywidgets         8.0.0a4
    jedi               0.18.0
    jsonschema         3.2.0
    jupyter-client     6.1.12
    jupyter-core       4.7.1
    jupyterlab-widgets 2.0.0a1
    matplotlib-inline  0.1.2
    nbformat           5.1.3
    parso              0.8.2
    pexpect            4.8.0
    pickleshare        0.7.5
    pip                21.1.1
    prompt-toolkit     3.0.18
    ptyprocess         0.7.0
    Pygments           2.9.0
    pyrsistent         0.17.3
    python-dateutil    2.8.1
    pyzmq              22.1.0
    setuptools         56.0.0
    six                1.16.0
    tornado            6.1
    traitlets          5.0.5
    wcwidth            0.2.5
    widgetsnbextension 4.0.0a2
Command Line Output
[TerminalIPythonApp] IPYTHONDIR set to: /home/XXXX/.ipython
[TerminalIPythonApp] Using existing profile dir: '/home/XXXX/.ipython/profile_default'
[TerminalIPythonApp] Searching path ['/tmp/ipywidgets_bug', '/home/XXXX/.ipython/profile_default', '/tmp/ipywidgets_bug/.venv/etc/ipython', '/usr/local/etc/ipython', '/etc/ipython'] for config files
[TerminalIPythonApp] Attempting to load config file: ipython_config.py
[TerminalIPythonApp] Looking for ipython_config in /etc/ipython
[TerminalIPythonApp] Looking for ipython_config in /usr/local/etc/ipython
[TerminalIPythonApp] Looking for ipython_config in /tmp/ipywidgets_bug/.venv/etc/ipython
[TerminalIPythonApp] Looking for ipython_config in /home/XXXX/.ipython/profile_default
[TerminalIPythonApp] Looking for ipython_config in /tmp/ipywidgets_bug

[TerminalIPythonApp] Loading IPython extensions...
[TerminalIPythonApp] Loading IPython extension: storemagic
[TerminalIPythonApp] Running file in user namespace: /tmp/ipywidgets_bug/example.ipynb
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
/tmp/ipywidgets_bug/example.ipynb in 
      2 o = ipywidgets.Output()
      3 with o:
----> 4     raise Exception()
      5 display(o)
      6 print("execution continues")

Exception: 
Output()
execution continues
[TerminalIPythonApp] IPython not interactive...
jtpio commented 3 years ago

Thanks @yumasheta.

Just to double check, is this only happening in the 8.0 alpha release? Or is it also an issue with 7.x?

yumasheta commented 3 years ago

Yes, it does happen with the current version (7.6.3) on PYPI as well. Just verified it.

jtpio commented 3 years ago

ok thanks for checking :+1:

jasongrout commented 3 years ago

Here is the relevant code:

https://github.com/jupyter-widgets/ipywidgets/blob/4132414e5a4615a78cc6b849824a6df0155b26e9/ipywidgets/widgets/widget_output.py#L132-L134

Can you say more about your situation, and why you want the exception propagated out of the context handler rather than just handled and displayed, as the code above indicates?

yumasheta commented 3 years ago

Ok, so maybe I'm missing something. I've been struggling a bit with how the output context works and is meant to work. I'd like a way to handle exceptions that occur inside the output context in the surrounding code. But if that exception never leaves the context that's not possible. So basically:

output = ipywidgets.Output()

try:
    with output:
        raise Exception
except:
    print("error")

But this doesn't work.

If you don't mind me talking about my implementation: I have some custom ipywidgets that use the Output context so that they can clear it and replace content in the same output. So it might not be a problem when using the jupyter notebook interactively (that is: exceptions silently fail), because you'll see the output anyway. However, it'd be nice to have an options to handle those exceptions. In particular I have unit tests that execute pre-defined notebook code (via ipython) that goes through the features of the widgets and has to fail on exceptions. But as you can imagine, this doesn't happen.

TL,DR: I want to "unit test" custom widgets for jupyter. I need a way to execute (and manipulate) my widgets programatically and test for errors. The widgets put their output in Output contexts, such that exceptions within those don't lead to failed tests.

wookayin commented 2 years ago

This would be, in my opinion, a breaking change on the behavior of ipywidgets.Output() -- so I think it would be worth considering shipping in 8.0 rather than 8.1. @vidartf, Any potential possibilities of including this earlier?

To me, the current behavior sounds really strange. It was not supposed to suppress exceptions, and the use of Output can change the control flow of user program.

(Example code: suppressed as the rationale is same as the OP)

More specifically,

def foo():
    with output:  # block 1
        print("Hello")
        data = some_logic_that_throws_an_exception()
        print("this will not be executed")

    with output:  # block 2
        print("data = ", data)

This will first show a stacktrace for the Exception from the first block. The rest of the code, especially block 2 should NOT be executed, but it does, and another stacktrace will be shown for an exception thrown when accessing data (UnboundLocalError: local variable 'data' referenced before assignment).

In addition, whether ipython is involved or not does change the behavior and flow of the same code.

Alternatively, we may have a configurable option whether to suppress exception or not if we want to keep a backward compatibility), but I suggest we should correct the behavior as we deliver a major release.

wookayin commented 2 years ago

I submitted a PR #3417 that would fix this one. My proposal is to change the behavior on whether exceptions won't be suppressed by default, but this would be up to core developers' decision. Thank you for your considerations.

vidartf commented 2 years ago

Thanks for the PR @wookayin . As a placeholder, you can also create a new widget by inheritance (no action in ipywidgets repo needed):

class NocatchOutput(ipywidgets.Output):
    def __exit__(self, *args, **kwargs):
        super().__exit__(*args, **kwargs)
DiegoF90 commented 1 year ago

@vidartf many thanks for the workaround. This seems to be printing the Traceback in red inside the Output, disregarding if the exception is later handled. Is there any way to avoid this?

xguse commented 1 year ago

So this weird behavior has not been fixed yet? I am still trying to understand why the user would expect or want an output context to essentially redirect stderr to /dev/null. It is the definition of failing ungracefully. Any updates?