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

PR: Make %matplotlib magic to really change backends #4246

Merged
merged 4 commits into from
Mar 11, 2017
Merged
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
48 changes: 40 additions & 8 deletions spyder/plugins/tests/test_ipythonconsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,37 @@ def close_widget():
#==============================================================================
# Tests
#==============================================================================
@flaky(max_runs=10)
@pytest.mark.skipif(os.name == 'nt', reason="It times out on Windows")
def test_mpl_backend_change(ipyconsole, qtbot):
"""
Test that Matplotlib backend is changed correctly when
using the %matplotlib magic
"""
shell = ipyconsole.get_current_shellwidget()
client = ipyconsole.get_current_client()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

# Import Matplotlib
with qtbot.waitSignal(shell.executed):
shell.execute('import matplotlib.pyplot as plt')

# Generate an inline plot
with qtbot.waitSignal(shell.executed):
shell.execute('plt.plot(range(10))')

# Change backends
with qtbot.waitSignal(shell.executed):
shell.execute('%matplotlib tk')

# Generate another plot
with qtbot.waitSignal(shell.executed):
shell.execute('plt.plot(range(10))')

# Assert that there's a single inline plot in the console
assert shell._control.toHtml().count('img src') == 1


@flaky(max_runs=10)
@pytest.mark.skipif(os.name == 'nt', reason="It times out on Windows")
def test_forced_restart_kernel(ipyconsole, qtbot):
Expand All @@ -71,12 +102,13 @@ def test_forced_restart_kernel(ipyconsole, qtbot):
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

# Do an assigment to verify that it's not there after restarting
shell.execute('a = 10')
qtbot.wait(500)
with qtbot.waitSignal(shell.executed):
shell.execute('a = 10')

# Generate a traceback and enter debugging mode
shell.execute('1/0')
qtbot.wait(500)
with qtbot.waitSignal(shell.executed):
shell.execute('1/0')

shell.execute('%debug')
qtbot.wait(500)

Expand All @@ -99,8 +131,8 @@ def test_restart_kernel(ipyconsole, qtbot):
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

# Do an assigment to verify that it's not there after restarting
shell.execute('a = 10')
qtbot.wait(500)
with qtbot.waitSignal(shell.executed):
shell.execute('a = 10')

# Restart kernel and wait until it's up again
shell._prompt_html = None
Expand Down Expand Up @@ -176,8 +208,8 @@ def test_load_kernel_file(ipyconsole, qtbot):

new_client = ipyconsole.get_clients()[1]
new_shell = new_client.shellwidget
new_shell.execute('a = 10')
qtbot.wait(500)
with qtbot.waitSignal(new_shell.executed):
new_shell.execute('a = 10')

assert new_client.name == '1/B'
assert shell.get_value('a') == new_shell.get_value('a')
Expand Down
24 changes: 6 additions & 18 deletions spyder/widgets/ipythonconsole/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ def configure_shellwidget(self, give_focus=True):
self.shellwidget.executing.connect(self.add_to_history)

# For Mayavi to run correctly
self.shellwidget.executing.connect(self.set_backend_for_mayavi)
self.shellwidget.executing.connect(
self.shellwidget.set_backend_for_mayavi)

# To update history after execution
self.shellwidget.executed.connect(self.update_history)
Expand All @@ -182,6 +183,10 @@ def configure_shellwidget(self, give_focus=True):
self.shellwidget.sig_dbg_kernel_restart.connect(
self.restart_kernel)

# To correctly change Matplotlib backend interactively
self.shellwidget.executing.connect(
self.shellwidget.change_mpl_backend)

def enable_stop_button(self):
self.stop_button.setEnabled(True)

Expand Down Expand Up @@ -405,23 +410,6 @@ def reset_namespace(self):
def update_history(self):
self.history = self.shellwidget._history

def set_backend_for_mayavi(self, command):
"""
Mayavi plots require the Qt backend, so we try to detect if one is
generated to change backends
"""
calling_mayavi = False
lines = command.splitlines()
for l in lines:
if not l.startswith('#'):
if 'import mayavi' in l or 'from mayavi' in l:
calling_mayavi = True
break
if calling_mayavi:
message = _("Changing backend to Qt for Mayavi")
self.shellwidget._append_plain_text(message + '\n')
self.shellwidget.execute("%gui inline\n%gui qt")

#------ Private API -------------------------------------------------------
def _create_loading_page(self):
"""Create html page to show while the kernel is starting"""
Expand Down
29 changes: 29 additions & 0 deletions spyder/widgets/ipythonconsole/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,35 @@ def handle_exec_method(self, msg):
# Remove method after being processed
self._kernel_methods.pop(expression)

def set_backend_for_mayavi(self, command):
"""
Mayavi plots require the Qt backend, so we try to detect if one is
generated to change backends
"""
calling_mayavi = False
lines = command.splitlines()
for l in lines:
if not l.startswith('#'):
if 'import mayavi' in l or 'from mayavi' in l:
calling_mayavi = True
break
if calling_mayavi:
message = _("Changing backend to Qt for Mayavi")
self._append_plain_text(message + '\n')
self.silent_execute("%gui inline\n%gui qt")

def change_mpl_backend(self, command):
"""
If the user is trying to change Matplotlib backends with
%matplotlib, send the same command again to the kernel to
correctly change it.

Fixes issue 4002
"""
if command.startswith('%matplotlib') and \
len(command.splitlines()) == 1:
self.silent_execute(command)

#---- Private methods (overrode by us) ---------------------------------
def _context_menu_make(self, pos):
"""Reimplement the IPython context menu"""
Expand Down