Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds error handling around install_repl_displayhook and call to enable_gui in pyplot; adds a test in test_backends_interactive. #25870

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

turnipseason
Copy link
Contributor

PR summary

Closes #23770. Starting an ipython session with InteractiveShell led to a crash with NotImplementedError being raised. An ImportError needed to be raised from that, allowing for continuation with a different backend.

PR checklist

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for opening your first PR into Matplotlib!

If you have not heard from us in a while, please feel free to ping @matplotlib/developers or anyone who has commented on the PR. Most of our reviewers are volunteers and sometimes things fall through the cracks.

You can also join us on gitter for real-time discussion.

For details on testing, writing docs, and our review process, please see the developer guide

We strive to be a welcoming and open project. Please follow our Code of Conduct.

install_repl_displayhook()
try:
install_repl_displayhook()
except ImportError as err:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we catch the NotImplemented error here instead and make no changes to install_repl_displayhook?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I changed it! In light of your next comment -- is this still relevant in general (should I amend my PR)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think the technical question is if we

  • eat the error
  • warn
  • re-raise ImportError

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearly I'm no authority on this, but warning and re-raising seem pretty logical to do because it just crashes otherwise.

I also didn't fully understand your comment -- like why should there be no GUI at all with block=True/sleep enabled. The couple of examples I tried all seemed to work (switching to a different backend and continuing as intended). I'm very likely missing something, but is the expected behavior here something different?

@tacaswell
Copy link
Member

Now that I see this implemented as I described I'm no longer sure this is best approach 😞 .

I still think the analysis of the problem in #23770 (comment) is correct, however what I did not consider when I wrote that is that it only applies if the user wants to do plt.ion() and work in "interactive mode". If they are happy to not have windows pop-up and be live while they type (and either use plt.show(block=True) or plt.sleep(10) to get temporarily live figures) then this change will prevent users from getting GUIs at all.

On the other hand if we just catch and eat the NotImplemented exception in switch_backends (maybe printing a warning) then we enable users use GUI backends with this limited IPython shell, even if its usage is degraded.

With the plain Python shell this sort of "un-managed" situation is possible so from a parity point of view preventing it is bad. On the other hand, currently the GUI backends do not work with this version of the IPython shell so we are not actually taking anything away, but are making it so things will automatically import and do something sensible.

I can see arguments both ways and think either one would be acceptable, but we should consider both options.

@tacaswell tacaswell modified the milestones: v3.8.0, v3.7.2 May 12, 2023
@tacaswell
Copy link
Member

So with this change I think it will fallback to agg because we can get IPython to setup up the input hook so that

plt.ion()
plt.show(block=False)

work. On the other hand, we could just eat the exception and give the user a partially functional GUI.

@melissawm
Copy link
Member

Hi @turnipseason @tacaswell do we need more discussion here or is there a clear path forward?

@turnipseason do you need help with the current test failures?

@turnipseason
Copy link
Contributor Author

Hi @turnipseason @tacaswell do we need more discussion here or is there a clear path forward?

@turnipseason do you need help with the current test failures?

Hi @melissawm ! Sorry for not replying sooner. I tried understanding what's going on but still don't quite get it, so I'm not sure I can contribute much to the discussion, unfortunately. When I tried running it, both (eating/not eating the exception) ways worked just fine and neither defaulted to the non-interactive background. They also never seemed to enter the interactive python mode though (I couldn't type in the console, even though the input was captured and shown after I closed the GUI), so I'm not sure if I just wrote something incorrectly.

With regards to the test failures -- yes, I think I do.
They seem to be coming from the tests I wrote, since it calls for IPython in the subprocess and the tests fail with a ModuleNotFoundError because there's no IPython. Should I check for the existence of IPython in the subprocess and skip the test somehow if is isn't found since this issue concerns IPython in the first place? Or rewrite them completely?

@ksunden
Copy link
Member

ksunden commented Jun 8, 2023

@turnipseason

yes you can use pytest.importorskip("IPython")

I would also perhaps suggest using the _run_helper for running your code as a subprocess, that will allow the code to look like a normal python function, rather than being included as strings.

@QuLogic QuLogic modified the milestones: v3.7.2, v3.7.3 Jul 5, 2023
@QuLogic QuLogic modified the milestones: v3.7.3, v3.8.0 Sep 9, 2023
@ksunden
Copy link
Member

ksunden commented Sep 12, 2023

Did the rebase, which was just caused by a comment being reflowed adjacent to some changed lines

ksunden
ksunden previously approved these changes Sep 12, 2023
@ksunden ksunden dismissed their stale review September 12, 2023 21:20

Test failures on azure macos are the newly added test

@ksunden ksunden modified the milestones: v3.8.0, v3.8.1 Sep 12, 2023
Comment on lines 639 to 640
response = _run_helper(_fallback_check, timeout=_test_timeout)
assert response != subprocess.CalledProcessError
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The run helper passes check=True, which causes CalledProcessError to be raised; I don't understand why this is checking the return value, which should be a subprocess.CompletedProcess.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I just thought we had to check for it. Should I leave the test at this, then? It seems to do the same thing (fail without the fix, pass with the fix).

def test_fallback_to_different_backend():
    pytest.importorskip("IPython") 
    _run_helper(_fallback_check, timeout=_test_timeout)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All exceptions are errors; the assert can never be False, so there's no need for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok then, thank you, I'll leave it without the assert!

@ksunden
Copy link
Member

ksunden commented Oct 27, 2023

Test failures on Azure MacOS are still the newly added test in the latest revision.

Additionally, needs a rebase for the meson build system change to fix Circle.

@ksunden ksunden modified the milestones: v3.8.1, v3.8.2 Oct 27, 2023
@ksunden ksunden modified the milestones: v3.8.2, v3.8.3 Nov 17, 2023
@tacaswell tacaswell marked this pull request as draft January 25, 2024 14:48
@tacaswell tacaswell modified the milestones: v3.8.3, future releases Jan 25, 2024
@QuLogic
Copy link
Member

QuLogic commented Oct 17, 2024

Do you have any thoughts on this PR, @ianthomas23?

@ianthomas23
Copy link
Member

Do you have any thoughts on this PR, @ianthomas23?

I think it is a somewhat unusual use case that would be reasonable to "won't fix" but given that the fix is simple, localised and evidently a good thing to do anyway then I would approve it. It needs a rebase but that looks relatively straightforward.

One complication is that there is another unprotected use of install_repl_displayhook in pyplot.ion():

stack = ExitStack()
stack.callback(ion if isinteractive() else ioff)
matplotlib.interactive(True)
install_repl_displayhook()
return stack

and we cannot repeat the fix here as we are not switching a backend. But I don't think it should stop this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Needs decision
Development

Successfully merging this pull request may close these issues.

[Bug]: crash due to backend issue in ipython session started explicitly with InteractiveShell
6 participants