From f45707d9e6111a83d2c741530cbff2be2c8005ff Mon Sep 17 00:00:00 2001 From: James Addison Date: Sun, 9 Mar 2025 13:03:16 +0000 Subject: [PATCH 1/2] FIX: pyplot auto-backend detection case-sensitivity fixup --- lib/matplotlib/pyplot.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index ab33146d2185..9957a76d0856 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 From 2ed08c185c727f17a9b8d16ad8cb0c31cc48a0de Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 11 Mar 2025 12:47:31 +0000 Subject: [PATCH 2/2] TST: coverage of backend fallback in headless mode Co-authored-by: Ian Thomas --- lib/matplotlib/tests/test_rcparams.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 096c5cef6cb8..334bb42ea12f 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")