-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
backend switching -- don't create a public fallback API #11600
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
Changes from all commits
56acb0f
ec77ca2
d5576e2
22508d8
0ccc19e
29dd9d4
97683a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,9 @@ | |
The object-oriented API is recommended for more complex plots. | ||
""" | ||
|
||
import importlib | ||
import inspect | ||
import logging | ||
from numbers import Number | ||
import re | ||
import sys | ||
|
@@ -29,7 +31,7 @@ | |
import matplotlib | ||
import matplotlib.colorbar | ||
import matplotlib.image | ||
from matplotlib import style | ||
from matplotlib import rcsetup, style | ||
from matplotlib import _pylab_helpers, interactive | ||
from matplotlib.cbook import ( | ||
dedent, deprecated, silent_list, warn_deprecated, _string_to_bool) | ||
|
@@ -67,10 +69,13 @@ | |
MaxNLocator | ||
from matplotlib.backends import pylab_setup | ||
|
||
_log = logging.getLogger(__name__) | ||
|
||
|
||
## Backend detection ## | ||
|
||
|
||
# FIXME: Deprecate. | ||
def _backend_selection(): | ||
""" | ||
If rcParams['backend_fallback'] is true, check to see if the | ||
|
@@ -110,8 +115,6 @@ def _backend_selection(): | |
## Global ## | ||
|
||
|
||
_backend_mod, new_figure_manager, draw_if_interactive, _show = pylab_setup() | ||
|
||
_IP_REGISTERED = None | ||
_INSTALL_FIG_OBSERVER = False | ||
|
||
|
@@ -213,21 +216,60 @@ def findobj(o=None, match=None, include_self=True): | |
|
||
def switch_backend(newbackend): | ||
""" | ||
Switch the default backend. This feature is **experimental**, and | ||
is only expected to work switching to an image backend. e.g., if | ||
you have a bunch of PostScript scripts that you want to run from | ||
an interactive ipython session, you may want to switch to the PS | ||
backend before running them to avoid having a bunch of GUI windows | ||
popup. If you try to interactively switch from one GUI backend to | ||
another, you will explode. | ||
Close all open figures and set the Matplotlib backend. | ||
|
||
Calling this command will close all open windows. | ||
The argument is case-insensitive. Switching to an interactive backend is | ||
possible only if no event loop for another interactive backend has started. | ||
Switching to and from non-interactive backends is always possible. | ||
|
||
Parameters | ||
---------- | ||
newbackend : str | ||
The name of the backend to use. | ||
""" | ||
close('all') | ||
close("all") | ||
|
||
if newbackend is rcsetup._auto_backend_sentinel: | ||
for candidate in ["macosx", "qt5agg", "qt4agg", "gtk3agg", "gtk3cairo", | ||
"tkagg", "wxagg", "agg", "cairo"]: | ||
try: | ||
switch_backend(candidate) | ||
except ImportError: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this catch the 'no display' errors you get on a headless server? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should(?), because _get_running_interactive_framework will return "headless" which is different from what the backend declares. We need the backend import to correctly fail with an ImportError, but I took care of the cases I encountered in the previous PR. |
||
continue | ||
else: | ||
return | ||
|
||
backend_name = ( | ||
newbackend[9:] if newbackend.startswith("module://") | ||
else "matplotlib.backends.backend_{}".format(newbackend.lower())) | ||
|
||
backend_mod = importlib.import_module(backend_name) | ||
Backend = type( | ||
"Backend", (matplotlib.backends._Backend,), vars(backend_mod)) | ||
_log.info("Loaded backend %s version %s.", | ||
newbackend, Backend.backend_version) | ||
|
||
required_framework = Backend.required_interactive_framework | ||
current_framework = \ | ||
matplotlib.backends._get_running_interactive_framework() | ||
if (current_framework and required_framework | ||
and current_framework != required_framework): | ||
raise ImportError( | ||
"Cannot load backend {!r} which requires the {!r} interactive " | ||
"framework, as {!r} is currently running".format( | ||
newbackend, required_framework, current_framework)) | ||
|
||
rcParams['backend'] = rcParamsDefault['backend'] = newbackend | ||
|
||
global _backend_mod, new_figure_manager, draw_if_interactive, _show | ||
matplotlib.use(newbackend, warn=False, force=True) | ||
from matplotlib.backends import pylab_setup | ||
_backend_mod, new_figure_manager, draw_if_interactive, _show = pylab_setup() | ||
_backend_mod = backend_mod | ||
new_figure_manager = Backend.new_figure_manager | ||
draw_if_interactive = Backend.draw_if_interactive | ||
_show = Backend.show | ||
|
||
# Need to keep a global reference to the backend for compatibility reasons. | ||
# See https://github.com/matplotlib/matplotlib/issues/6092 | ||
matplotlib.backends.backend = newbackend | ||
|
||
|
||
def show(*args, **kw): | ||
|
@@ -2364,6 +2406,9 @@ def _autogen_docstring(base): | |
# to determine if they should trigger a draw. | ||
install_repl_displayhook() | ||
|
||
# Set up the backend. | ||
switch_backend(rcParams["backend"]) | ||
|
||
|
||
################# REMAINING CONTENT GENERATED BY boilerplate.py ############## | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
import operator | ||
import os | ||
import re | ||
import sys | ||
|
||
from matplotlib import cbook | ||
from matplotlib.cbook import ls_mapper | ||
|
@@ -242,13 +243,14 @@ def validate_fonttype(s): | |
|
||
_validate_standard_backends = ValidateInStrings( | ||
'backend', all_backends, ignorecase=True) | ||
_auto_backend_sentinel = object() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you may want to fix There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious as to why this used as the sentinel, instead of the more typical 'auto' or None. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because we don't want to expose this as a public API (... at this point yet, at least). |
||
|
||
|
||
def validate_backend(s): | ||
if s.startswith('module://'): | ||
return s | ||
else: | ||
return _validate_standard_backends(s) | ||
backend = ( | ||
s if s is _auto_backend_sentinel or s.startswith("module://") | ||
else _validate_standard_backends(s)) | ||
return backend | ||
|
||
|
||
def validate_qt4(s): | ||
|
@@ -965,9 +967,8 @@ def _validate_linestyle(ls): | |
|
||
# a map from key -> value, converter | ||
defaultParams = { | ||
'backend': ['Agg', validate_backend], # agg is certainly | ||
# present | ||
'backend_fallback': [True, validate_bool], # agg is certainly present | ||
'backend': [_auto_backend_sentinel, validate_backend], | ||
'backend_fallback': [True, validate_bool], | ||
'backend.qt4': [None, validate_qt4], | ||
'backend.qt5': [None, validate_qt5], | ||
'webagg.port': [8988, validate_int], | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought this docstring reference to 'switch_backend' was going to be deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I missed that (but did remove it from warning message.