From adfb193d790875a7002194a3b66dbbb008e1a27f Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 20 Aug 2018 09:08:22 +0200 Subject: [PATCH 01/12] Resolve backend in rcParams.__getitem__("backend"). ... to avoid leaking the non-string sentinel. Changes to `backends/__init__` and `_backend_selection()` were necessary to avoid import-time cycles. The fallback order in `backend_fallback` slightly changed in that the FooCairo backends now fallback to WxAgg instead of Wx when a wx event loop is active, but I wouldn't worry too much about it anyways given that the Wx backend is deprecated and the correct fallback would be WxCairo to start with. --- lib/matplotlib/__init__.py | 10 ++++++-- lib/matplotlib/backends/__init__.py | 9 +++---- lib/matplotlib/pyplot.py | 40 ++++++++--------------------- lib/matplotlib/testing/__init__.py | 2 +- lib/matplotlib/testing/conftest.py | 2 +- 5 files changed, 23 insertions(+), 40 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 24123b529033..1befdd987106 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -137,7 +137,7 @@ # cbook must import matplotlib only within function # definitions, so it is safe to import from it here. -from . import cbook +from . import cbook, rcsetup from matplotlib.cbook import ( MatplotlibDeprecationWarning, dedent, get_label, sanitize_sequence) from matplotlib.cbook import mplDeprecation # deprecated @@ -877,6 +877,12 @@ def __getitem__(self, key): "3.0", "{} is deprecated; in the future, examples will be " "found relative to the 'datapath' directory.".format(key)) + elif key == "backend": + val = dict.__getitem__(self, key) + if val is rcsetup._auto_backend_sentinel: + from matplotlib import pyplot as plt + plt.switch_backend(rcsetup._auto_backend_sentinel) + return dict.__getitem__(self, key) def __repr__(self): @@ -1357,7 +1363,7 @@ def use(arg, warn=True, force=False): if os.environ.get('MPLBACKEND'): - use(os.environ['MPLBACKEND']) + rcParams['backend'] = os.environ.get('MPLBACKEND') def get_backend(): diff --git a/lib/matplotlib/backends/__init__.py b/lib/matplotlib/backends/__init__.py index e4e6082e7d79..01e230df6804 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -10,12 +10,9 @@ _log = logging.getLogger(__name__) -backend = matplotlib.get_backend() -# FIXME: Remove. -_backend_loading_tb = "".join( - line for line in traceback.format_stack() - # Filter out line noise from importlib line. - if not line.startswith(' File " Date: Wed, 22 Aug 2018 14:43:22 -0400 Subject: [PATCH 02/12] TST: add a force to a use call to be safe --- lib/matplotlib/tests/test_backend_svg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_backend_svg.py b/lib/matplotlib/tests/test_backend_svg.py index ea4070cc3cbe..835ff01bb5bd 100644 --- a/lib/matplotlib/tests/test_backend_svg.py +++ b/lib/matplotlib/tests/test_backend_svg.py @@ -139,7 +139,7 @@ def test_determinism(filename, usetex): [sys.executable, '-R', '-c', 'import matplotlib; ' 'matplotlib._called_from_pytest = True; ' - 'matplotlib.use("svg"); ' + 'matplotlib.use("svg", force=True); ' 'from matplotlib.tests.test_backend_svg ' 'import _test_determinism_save;' '_test_determinism_save(%r, %r)' % (filename, usetex)], From adf696fe8c16a48061e77743a7fae91a3f0fda4e Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 22 Aug 2018 14:48:37 -0400 Subject: [PATCH 03/12] API: change type of matplotlib.rcParamsOrig from dict -> RcParams This is to ensure that when IPython get the backend in IPython.core.pylabtools.find_gui_and_backend the backend resolution is triggered. --- lib/matplotlib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 1befdd987106..fdcf185b991b 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1097,10 +1097,10 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): _fullpath = os.path.join(_basedir, rcParams['examples.directory']) rcParams['examples.directory'] = _fullpath -rcParamsOrig = rcParams.copy() with warnings.catch_warnings(): warnings.simplefilter("ignore", MatplotlibDeprecationWarning) + rcParamsOrig = RcParams(rcParams.copy()) rcParamsDefault = RcParams([(key, default) for key, (default, converter) in defaultParams.items() if key not in _all_deprecated]) From 1e8788d35f78fa7190bb28a9ce3857db8b57ca17 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 22 Aug 2018 14:59:13 -0400 Subject: [PATCH 04/12] FIX: when resolving the auto-backend also update rcParamsOrig --- lib/matplotlib/pyplot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index e6e045275bad..cb08f95c9a7a 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -39,7 +39,7 @@ from matplotlib.backend_bases import FigureCanvasBase from matplotlib.figure import Figure, figaspect from matplotlib.gridspec import GridSpec -from matplotlib import rcParams, rcParamsDefault, get_backend +from matplotlib import rcParams, rcParamsDefault, get_backend, rcParamsOrig from matplotlib import rc_context from matplotlib.rcsetup import interactive_bk as _interactive_bk from matplotlib.artist import getp, get, Artist @@ -217,6 +217,7 @@ def switch_backend(newbackend): except ImportError: continue else: + rcParamsOrig['backend'] = candidate return backend_name = ( From ce04937c492dbcdf4714fa492b720423c13c5fcf Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 22 Aug 2018 14:59:37 -0400 Subject: [PATCH 05/12] API: Do not have rc_file_defaults import resolve backends In either case (resolved or not) pass the backend value through. This maintains the behavior prior to changing the type of rcParamsOrig to RcParams. --- lib/matplotlib/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index fdcf185b991b..84e895f277b2 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1224,8 +1224,9 @@ def rc_file_defaults(): with warnings.catch_warnings(): warnings.simplefilter("ignore", mplDeprecation) from .style.core import STYLE_BLACKLIST - rcParams.update({k: v for k, v in rcParamsOrig.items() - if k not in STYLE_BLACKLIST}) + rcParams.update({k: rcParamsOrig[k] for k in rcParamsOrig + if k not in STYLE_BLACKLIST and k != 'backend'}) + rcParams['backend'] = dict.__getitem__(rcParamsOrig, 'backend') def rc_file(fname): From 7dffd5bf61af4c37533c2f4e273e6ba62abe7657 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 22 Aug 2018 15:13:08 -0400 Subject: [PATCH 06/12] MNT: special case the handling of 'backend' in rc_context If the context block triggers the auto-resolution of the backend keep than information around. --- lib/matplotlib/__init__.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 84e895f277b2..8fadc8703a52 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1292,16 +1292,23 @@ def __init__(self, rc=None, fname=None): if rc: rcParams.update(rc) except Exception: - # If anything goes wrong, revert to the original rcs. - dict.update(rcParams, self._orig) + self.__fallback() raise + def __fallback(self): + # If anything goes wrong, revert to the original rcs. + updated_backend = self._orig['backend'] + dict.update(rcParams, self._orig) + # except for the backend. If the context block triggered resloving + # the auto backend resolution keep that value around + if self._orig['backend'] is rcsetup._auto_backend_sentinel: + rcParams['backend'] = updated_backend + def __enter__(self): return self def __exit__(self, exc_type, exc_value, exc_tb): - # No need to revalidate the original values. - dict.update(rcParams, self._orig) + self.__fallback() def use(arg, warn=True, force=False): From 07b6c904ddc720bf874072614e0b25d8a819a22e Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 22 Aug 2018 15:14:58 -0400 Subject: [PATCH 07/12] MNT: RcParams.__setitem__ refuses to set the backend sentinel This is to prevent the auto resolution of the backend from being reverted. --- lib/matplotlib/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 8fadc8703a52..24071421932b 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -849,6 +849,10 @@ def __setitem__(self, key, val): cbook.warn_deprecated( "3.0", "{} is deprecated; in the future, examples will be " "found relative to the 'datapath' directory.".format(key)) + elif key == 'backend': + if val is rcsetup._auto_backend_sentinel: + if 'backend' in self: + return try: cval = self.validate[key](val) except ValueError as ve: From 2c1f52c3dec9a3b9b73174c14ee6ab5c4ac291e6 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 22 Aug 2018 15:55:32 -0400 Subject: [PATCH 08/12] FIX: when calling use do not trigger auto-backend resolution --- lib/matplotlib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 24071421932b..97a83adde4a0 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1345,7 +1345,7 @@ def use(arg, warn=True, force=False): name = validate_backend(arg) # if setting back to the same thing, do nothing - if (rcParams['backend'] == name): + if (dict.__getitem__(rcParams, 'backend') == name): pass # Check if we have already imported pyplot and triggered From 75cc617593f2f9d40af4dc68698d45038aafcf54 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 22 Aug 2018 16:12:18 -0400 Subject: [PATCH 09/12] MNT: special case 'backend' in rc_file --- lib/matplotlib/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 97a83adde4a0..2414f8216e29 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1245,8 +1245,13 @@ def rc_file(fname): with warnings.catch_warnings(): warnings.simplefilter("ignore", mplDeprecation) from .style.core import STYLE_BLACKLIST - rcParams.update({k: v for k, v in rc_params_from_file(fname).items() - if k not in STYLE_BLACKLIST}) + rc_from_file = rc_params_from_file(fname) + rcParams.update({k: rc_from_file[k] for k in rc_from_file + if k not in STYLE_BLACKLIST and k != 'backend'}) + + proposed_backend = dict.__getitem__(rc_from_file, 'backend') + if (proposed_backend is not rcsetup._auto_backend_sentinel): + rcParams['backend'] = proposed_backend class rc_context: From aaab933c95c6d446dd4e900f9742cbc8dd6c0ce9 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 22 Aug 2018 16:14:54 -0400 Subject: [PATCH 10/12] TST: add check at end of auto-fixture that backend is still agg --- lib/matplotlib/testing/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/testing/conftest.py b/lib/matplotlib/testing/conftest.py index 40ce56c4895a..8aaebb22e2df 100644 --- a/lib/matplotlib/testing/conftest.py +++ b/lib/matplotlib/testing/conftest.py @@ -53,6 +53,8 @@ def mpl_test_settings(request): if backend is not None: plt.switch_backend(prev_backend) + assert matplotlib.get_backend() == 'agg' + @pytest.fixture def mpl_image_comparison_parameters(request, extension): From a2bc1b931a6d6261f3db05761ca7ed199350e669 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 27 Aug 2018 15:09:38 -0400 Subject: [PATCH 11/12] DOC: do not point to pyplot from __init__.py --- lib/matplotlib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 2414f8216e29..99ea1c32bce8 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1343,7 +1343,7 @@ def use(arg, warn=True, force=False): force : bool, optional If True, attempt to switch the backend. This defaults to - false and using `.pyplot.switch_backend` is preferred. + False. """ From ad91933c16261026c3b7e547b0c6b0ec02d58c67 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 27 Aug 2018 15:10:27 -0400 Subject: [PATCH 12/12] MNT: do not special-case backend in rc_file_defaults or rc_file The 'backend' rcParam is already skipped by the STYLE_BLACKLIST. --- lib/matplotlib/__init__.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 99ea1c32bce8..1e87a0a968d4 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1229,8 +1229,7 @@ def rc_file_defaults(): warnings.simplefilter("ignore", mplDeprecation) from .style.core import STYLE_BLACKLIST rcParams.update({k: rcParamsOrig[k] for k in rcParamsOrig - if k not in STYLE_BLACKLIST and k != 'backend'}) - rcParams['backend'] = dict.__getitem__(rcParamsOrig, 'backend') + if k not in STYLE_BLACKLIST}) def rc_file(fname): @@ -1247,11 +1246,7 @@ def rc_file(fname): from .style.core import STYLE_BLACKLIST rc_from_file = rc_params_from_file(fname) rcParams.update({k: rc_from_file[k] for k in rc_from_file - if k not in STYLE_BLACKLIST and k != 'backend'}) - - proposed_backend = dict.__getitem__(rc_from_file, 'backend') - if (proposed_backend is not rcsetup._auto_backend_sentinel): - rcParams['backend'] = proposed_backend + if k not in STYLE_BLACKLIST}) class rc_context: