From 5090b264dbc60899ecef0cd0e0e4252668bd0542 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 14 Mar 2025 08:38:45 +0000 Subject: [PATCH] Backport PR #29721: FIX: pyplot auto-backend detection case-sensitivity fixup --- lib/matplotlib/pyplot.py | 15 +++++++++------ lib/matplotlib/tests/test_rcparams.py | 25 ++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 48aea1b3bd9c..83b380505d5b 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2715,12 +2715,15 @@ def polar(*args, **kwargs) -> list[Line2D]: # If rcParams['backend_fallback'] is true, and an interactive backend is # requested, ignore rcParams['backend'] and force selection of a backend that # is compatible with the current running interactive framework. -if (rcParams["backend_fallback"] - and rcParams._get_backend_or_none() in ( # type: ignore[attr-defined] - set(backend_registry.list_builtin(BackendFilter.INTERACTIVE)) - - {'webagg', 'nbagg'}) - and cbook._get_running_interactive_framework()): - rcParams._set("backend", rcsetup._auto_backend_sentinel) +if rcParams["backend_fallback"]: + requested_backend = rcParams._get_backend_or_none() # type: ignore[attr-defined] + requested_backend = None if requested_backend is None else requested_backend.lower() + available_backends = backend_registry.list_builtin(BackendFilter.INTERACTIVE) + if ( + requested_backend in (set(available_backends) - {'webagg', 'nbagg'}) + and cbook._get_running_interactive_framework() + ): + rcParams._set("backend", rcsetup._auto_backend_sentinel) # fmt: on diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 13633956c349..bea5e90ea4e5 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -521,10 +521,11 @@ def test_rcparams_reset_after_fail(): @pytest.mark.skipif(sys.platform != "linux", reason="Linux only") -def test_backend_fallback_headless(tmp_path): +def test_backend_fallback_headless_invalid_backend(tmp_path): env = {**os.environ, "DISPLAY": "", "WAYLAND_DISPLAY": "", "MPLBACKEND": "", "MPLCONFIGDIR": str(tmp_path)} + # plotting should fail with the tkagg backend selected in a headless environment with pytest.raises(subprocess.CalledProcessError): subprocess_run_for_testing( [sys.executable, "-c", @@ -536,6 +537,28 @@ def test_backend_fallback_headless(tmp_path): env=env, check=True, stderr=subprocess.DEVNULL) +@pytest.mark.skipif(sys.platform != "linux", reason="Linux only") +def test_backend_fallback_headless_auto_backend(tmp_path): + # specify a headless mpl environment, but request a graphical (tk) backend + env = {**os.environ, + "DISPLAY": "", "WAYLAND_DISPLAY": "", + "MPLBACKEND": "TkAgg", "MPLCONFIGDIR": str(tmp_path)} + + # allow fallback to an available interactive backend explicitly in configuration + rc_path = tmp_path / "matplotlibrc" + rc_path.write_text("backend_fallback: true") + + # plotting should succeed, by falling back to use the generic agg backend + backend = subprocess_run_for_testing( + [sys.executable, "-c", + "import matplotlib.pyplot;" + "matplotlib.pyplot.plot(42);" + "print(matplotlib.get_backend());" + ], + env=env, text=True, check=True, capture_output=True).stdout + assert backend.strip().lower() == "agg" + + @pytest.mark.skipif( sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid(), reason="headless")