From bffe0bed55dec16881c76ed59318e4df0da2688f Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Tue, 30 Jan 2024 10:36:00 +0000 Subject: [PATCH 01/17] Add BackendRegistry singleton class --- lib/matplotlib/backend_bases.py | 16 ++--- lib/matplotlib/backends/meson.build | 1 + lib/matplotlib/backends/registry.py | 61 +++++++++++++++++++ lib/matplotlib/backends/registry.pyi | 16 +++++ lib/matplotlib/pyplot.py | 23 +++---- lib/matplotlib/rcsetup.py | 45 +++++++++----- lib/matplotlib/tests/test_backend_registry.py | 59 ++++++++++++++++++ lib/matplotlib/tests/test_matplotlib.py | 6 +- 8 files changed, 186 insertions(+), 41 deletions(-) create mode 100644 lib/matplotlib/backends/registry.py create mode 100644 lib/matplotlib/backends/registry.pyi create mode 100644 lib/matplotlib/tests/test_backend_registry.py diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index a353500f8725..39771e4170db 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -104,16 +104,12 @@ def _safe_pyplot_import(): current_framework = cbook._get_running_interactive_framework() if current_framework is None: raise # No, something else went wrong, likely with the install... - backend_mapping = { - 'qt': 'qtagg', - 'gtk3': 'gtk3agg', - 'gtk4': 'gtk4agg', - 'wx': 'wxagg', - 'tk': 'tkagg', - 'macosx': 'macosx', - 'headless': 'agg', - } - backend = backend_mapping[current_framework] + + from matplotlib.backends.registry import backendRegistry + backend = backendRegistry.framework_to_backend(current_framework) + if backend is None: + raise KeyError(backend) + rcParams["backend"] = mpl.rcParamsOrig["backend"] = backend import matplotlib.pyplot as plt # Now this should succeed. return plt diff --git a/lib/matplotlib/backends/meson.build b/lib/matplotlib/backends/meson.build index 050cc616b42c..1e3e47c0a915 100644 --- a/lib/matplotlib/backends/meson.build +++ b/lib/matplotlib/backends/meson.build @@ -33,6 +33,7 @@ python_sources = [ 'backend_wxagg.py', 'backend_wxcairo.py', 'qt_compat.py', + 'registry.py', ] typing_sources = [ diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py new file mode 100644 index 000000000000..b445a89f96f0 --- /dev/null +++ b/lib/matplotlib/backends/registry.py @@ -0,0 +1,61 @@ +from enum import Enum + + +class BackendFilter(Enum): + INTERACTIVE = 0 + INTERACTIVE_NON_WEB = 1 + NON_INTERACTIVE = 2 + + +class BackendRegistry: + """ + Registry of backends available within Matplotlib. + + This is the single source of truth for available backends. + """ + def __init__(self): + # Built-in backends are those which are included in the Matplotlib repo. + # A backend with name 'name' is located in the module + # f'matplotlib.backends.backend_{name.lower()}' + + # The capitalized forms are needed for ipython at present; this may + # change for later versions. + self._builtin_interactive = [ + "GTK3Agg", "GTK3Cairo", "GTK4Agg", "GTK4Cairo", + "MacOSX", + "nbAgg", + "QtAgg", "QtCairo", "Qt5Agg", "Qt5Cairo", + "TkAgg", "TkCairo", + "WebAgg", + "WX", "WXAgg", "WXCairo", + ] + self._builtin_not_interactive = [ + "agg", "cairo", "pdf", "pgf", "ps", "svg", "template", + ] + self._framework_to_backend_mapping = { + "qt": "qtagg", + "gtk3": "gtk3agg", + "gtk4": "gtk4agg", + "wx": "wxagg", + "tk": "tkagg", + "macosx": "macosx", + "headless": "agg", + } + + def framework_to_backend(self, interactive_framework): + return self._framework_to_backend_mapping.get(interactive_framework) + + def list_builtin(self, filter_=None): + if filter_ == BackendFilter.INTERACTIVE: + return self._builtin_interactive + elif filter_ == BackendFilter.INTERACTIVE_NON_WEB: + return list(filter(lambda x: x.lower() not in ("webagg", "nbagg"), + self._builtin_interactive)) + elif filter_ == BackendFilter.NON_INTERACTIVE: + return self._builtin_not_interactive + + return self._builtin_interactive + self._builtin_not_interactive + + +# Singleton +backendRegistry = BackendRegistry() diff --git a/lib/matplotlib/backends/registry.pyi b/lib/matplotlib/backends/registry.pyi new file mode 100644 index 000000000000..e5646c3d1bdc --- /dev/null +++ b/lib/matplotlib/backends/registry.pyi @@ -0,0 +1,16 @@ +from enum import Enum + + +class BackendFilter(Enum): + INTERACTIVE: int + INTERACTIVE_NON_WEB: int + NON_INTERACTIVE: int + + +class BackendRegistry: + def __init__(self) -> None: ... + def framework_to_backend(self, interactive_framework: str) -> str | None: ... + def list_builtin(self, filter_: BackendFilter | None) -> list[str]: ... + + +backendRegistry: BackendRegistry diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 2cf0d5325a63..3442c778571b 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -69,6 +69,7 @@ from matplotlib.artist import Artist from matplotlib.axes import Axes from matplotlib.axes import Subplot # noqa: F401 +from matplotlib.backends.registry import BackendFilter, backendRegistry from matplotlib.projections import PolarAxes from matplotlib import mlab # for detrend_none, window_hanning from matplotlib.scale import get_scale_names # noqa: F401 @@ -301,16 +302,10 @@ def switch_backend(newbackend: str) -> None: if newbackend is rcsetup._auto_backend_sentinel: current_framework = cbook._get_running_interactive_framework() - mapping = {'qt': 'qtagg', - 'gtk3': 'gtk3agg', - 'gtk4': 'gtk4agg', - 'wx': 'wxagg', - 'tk': 'tkagg', - 'macosx': 'macosx', - 'headless': 'agg'} - - if current_framework in mapping: - candidates = [mapping[current_framework]] + + if (current_framework and + (backend := backendRegistry.framework_to_backend(current_framework))): + candidates = [backend] else: candidates = [] candidates += [ @@ -2509,10 +2504,10 @@ def polar(*args, **kwargs) -> list[Line2D]: # 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(rcsetup.interactive_bk) - {'WebAgg', 'nbAgg'}) - and cbook._get_running_interactive_framework()): - rcParams._set("backend", rcsetup._auto_backend_sentinel) + and rcParams._get_backend_or_none() in ( # type: ignore + set(backendRegistry.list_builtin(BackendFilter.INTERACTIVE_NON_WEB))) + and cbook._get_running_interactive_framework()): # type: ignore + rcParams._set("backend", rcsetup._auto_backend_sentinel) # type: ignore # fmt: on diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index f730db0dee3b..214656c9e60b 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -23,6 +23,7 @@ import numpy as np from matplotlib import _api, cbook +from matplotlib.backends.registry import BackendFilter, backendRegistry from matplotlib.cbook import ls_mapper from matplotlib.colors import Colormap, is_color_like from matplotlib._fontconfig_pattern import parse_fontconfig_pattern @@ -32,20 +33,34 @@ from cycler import Cycler, cycler as ccycler -# The capitalized forms are needed for ipython at present; this may -# change for later versions. -interactive_bk = [ - 'GTK3Agg', 'GTK3Cairo', 'GTK4Agg', 'GTK4Cairo', - 'MacOSX', - 'nbAgg', - 'QtAgg', 'QtCairo', 'Qt5Agg', 'Qt5Cairo', - 'TkAgg', 'TkCairo', - 'WebAgg', - 'WX', 'WXAgg', 'WXCairo', -] -non_interactive_bk = ['agg', 'cairo', - 'pdf', 'pgf', 'ps', 'svg', 'template'] -all_backends = interactive_bk + non_interactive_bk +# Deprecation of module-level attributes using PEP 562 +_deprecated_interactive_bk = backendRegistry.list_builtin(BackendFilter.INTERACTIVE) +_deprecated_non_interactive_bk = backendRegistry.list_builtin( + BackendFilter.NON_INTERACTIVE) +_deprecated_all_backends = backendRegistry.list_builtin() + +_deprecated_names_and_args = { + "interactive_bk": "matplotlib.backends.registry.BackendFilter.INTERACTIVE", + "non_interactive_bk": "matplotlib.backends.registry.BackendFilter.NON_INTERACTIVE", + "all_backends": "", +} + + +def __getattr__(name): + if name in _deprecated_names_and_args: + arg = _deprecated_names_and_args[name] + _api.warn_deprecated( + "3.9.0", + name=name, + alternative="``matplotlib.backends.registry.backendRegistry" + f".list_builtin({arg})``", + ) + return globals()[f"_deprecated_{name}"] + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +def __dir__(): + return sorted(globals().keys() | _deprecated_names_and_args.keys()) class ValidateInStrings: @@ -256,7 +271,7 @@ def validate_fonttype(s): _validate_standard_backends = ValidateInStrings( - 'backend', all_backends, ignorecase=True) + 'backend', backendRegistry.list_builtin(), ignorecase=True) _auto_backend_sentinel = object() diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py new file mode 100644 index 000000000000..98bdd9bd2793 --- /dev/null +++ b/lib/matplotlib/tests/test_backend_registry.py @@ -0,0 +1,59 @@ +from collections.abc import Sequence +from typing import Any + +import pytest + +from matplotlib.backends.registry import BackendFilter, backendRegistry + + +def has_duplicates(seq: Sequence[Any]) -> bool: + return len(seq) > len(set(seq)) + + +@pytest.mark.parametrize( + 'framework,expected', + [ + ('qt', 'qtagg'), + ('gtk3', 'gtk3agg'), + ('gtk4', 'gtk4agg'), + ('wx', 'wxagg'), + ('tk', 'tkagg'), + ('macosx', 'macosx'), + ('headless', 'agg'), + ('does not exist', None), + ] +) +def test_framework_to_backend(framework, expected): + assert backendRegistry.framework_to_backend(framework) == expected + + +def test_list_builtin(): + backends = backendRegistry.list_builtin() + assert not has_duplicates(backends) + # Compare using sets as order is not important + assert set(backends) == set(( + 'GTK3Agg', 'GTK3Cairo', 'GTK4Agg', 'GTK4Cairo', 'MacOSX', 'nbAgg', 'QtAgg', + 'QtCairo', 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'TkCairo', 'WebAgg', 'WX', 'WXAgg', + 'WXCairo', 'agg', 'cairo', 'pdf', 'pgf', 'ps', 'svg', 'template', + )) + + +@pytest.mark.parametrize( + 'filter,expected', + [ + (BackendFilter.INTERACTIVE, + ['GTK3Agg', 'GTK3Cairo', 'GTK4Agg', 'GTK4Cairo', 'MacOSX', 'nbAgg', 'QtAgg', + 'QtCairo', 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'TkCairo', 'WebAgg', 'WX', 'WXAgg', + 'WXCairo']), + (BackendFilter.INTERACTIVE_NON_WEB, + ['GTK3Agg', 'GTK3Cairo', 'GTK4Agg', 'GTK4Cairo', 'MacOSX', 'QtAgg', 'QtCairo', + 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'TkCairo', 'WX', 'WXAgg', 'WXCairo']), + (BackendFilter.NON_INTERACTIVE, + ['agg', 'cairo', 'pdf', 'pgf', 'ps', 'svg', 'template']), + ] +) +def test_list_builtin_with_filter(filter, expected): + backends = backendRegistry.list_builtin(filter) + assert not has_duplicates(backends) + # Compare using sets as order is not important + assert set(backends) == set(expected) diff --git a/lib/matplotlib/tests/test_matplotlib.py b/lib/matplotlib/tests/test_matplotlib.py index a1aa4ec212d6..6baef7c07e88 100644 --- a/lib/matplotlib/tests/test_matplotlib.py +++ b/lib/matplotlib/tests/test_matplotlib.py @@ -57,10 +57,12 @@ def parse(key): backends += [e.strip() for e in line.split(',') if e] return backends + from matplotlib.backends.registry import BackendFilter, backendRegistry + assert (set(parse('- interactive backends:\n')) == - set(matplotlib.rcsetup.interactive_bk)) + set(backendRegistry.list_builtin(BackendFilter.INTERACTIVE))) assert (set(parse('- non-interactive backends:\n')) == - set(matplotlib.rcsetup.non_interactive_bk)) + set(backendRegistry.list_builtin(BackendFilter.NON_INTERACTIVE))) def test_importable_with__OO(): From acb7bf54fede713988b58989a6ae3b1109746a2e Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 31 Jan 2024 11:07:46 +0000 Subject: [PATCH 02/17] Use backend_registry for name of singleton instance --- lib/matplotlib/backend_bases.py | 4 ++-- lib/matplotlib/backends/registry.py | 2 +- lib/matplotlib/backends/registry.pyi | 2 +- lib/matplotlib/pyplot.py | 4 ++-- lib/matplotlib/rcsetup.py | 12 ++++++------ lib/matplotlib/tests/test_backend_registry.py | 8 ++++---- lib/matplotlib/tests/test_matplotlib.py | 6 +++--- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 39771e4170db..586900529cb2 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -105,8 +105,8 @@ def _safe_pyplot_import(): if current_framework is None: raise # No, something else went wrong, likely with the install... - from matplotlib.backends.registry import backendRegistry - backend = backendRegistry.framework_to_backend(current_framework) + from matplotlib.backends.registry import backend_registry + backend = backend_registry.framework_to_backend(current_framework) if backend is None: raise KeyError(backend) diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py index b445a89f96f0..d7c50b66f1d2 100644 --- a/lib/matplotlib/backends/registry.py +++ b/lib/matplotlib/backends/registry.py @@ -58,4 +58,4 @@ def list_builtin(self, filter_=None): # Singleton -backendRegistry = BackendRegistry() +backend_registry = BackendRegistry() diff --git a/lib/matplotlib/backends/registry.pyi b/lib/matplotlib/backends/registry.pyi index e5646c3d1bdc..e847ca690629 100644 --- a/lib/matplotlib/backends/registry.pyi +++ b/lib/matplotlib/backends/registry.pyi @@ -13,4 +13,4 @@ class BackendRegistry: def list_builtin(self, filter_: BackendFilter | None) -> list[str]: ... -backendRegistry: BackendRegistry +backend_registry: BackendRegistry diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 3442c778571b..329de8b3d26f 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -304,7 +304,7 @@ def switch_backend(newbackend: str) -> None: current_framework = cbook._get_running_interactive_framework() if (current_framework and - (backend := backendRegistry.framework_to_backend(current_framework))): + (backend := backend_registry.framework_to_backend(current_framework))): candidates = [backend] else: candidates = [] @@ -2505,7 +2505,7 @@ def polar(*args, **kwargs) -> list[Line2D]: # is compatible with the current running interactive framework. if (rcParams["backend_fallback"] and rcParams._get_backend_or_none() in ( # type: ignore - set(backendRegistry.list_builtin(BackendFilter.INTERACTIVE_NON_WEB))) + set(backend_registry.list_builtin(BackendFilter.INTERACTIVE_NON_WEB))) and cbook._get_running_interactive_framework()): # type: ignore rcParams._set("backend", rcsetup._auto_backend_sentinel) # type: ignore diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 214656c9e60b..6ef689241a6a 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -23,7 +23,7 @@ import numpy as np from matplotlib import _api, cbook -from matplotlib.backends.registry import BackendFilter, backendRegistry +from matplotlib.backends.registry import BackendFilter, backend_registry from matplotlib.cbook import ls_mapper from matplotlib.colors import Colormap, is_color_like from matplotlib._fontconfig_pattern import parse_fontconfig_pattern @@ -34,10 +34,10 @@ # Deprecation of module-level attributes using PEP 562 -_deprecated_interactive_bk = backendRegistry.list_builtin(BackendFilter.INTERACTIVE) -_deprecated_non_interactive_bk = backendRegistry.list_builtin( +_deprecated_interactive_bk = backend_registry.list_builtin(BackendFilter.INTERACTIVE) +_deprecated_non_interactive_bk = backend_registry.list_builtin( BackendFilter.NON_INTERACTIVE) -_deprecated_all_backends = backendRegistry.list_builtin() +_deprecated_all_backends = backend_registry.list_builtin() _deprecated_names_and_args = { "interactive_bk": "matplotlib.backends.registry.BackendFilter.INTERACTIVE", @@ -52,7 +52,7 @@ def __getattr__(name): _api.warn_deprecated( "3.9.0", name=name, - alternative="``matplotlib.backends.registry.backendRegistry" + alternative="``matplotlib.backends.registry.backend_registry" f".list_builtin({arg})``", ) return globals()[f"_deprecated_{name}"] @@ -271,7 +271,7 @@ def validate_fonttype(s): _validate_standard_backends = ValidateInStrings( - 'backend', backendRegistry.list_builtin(), ignorecase=True) + 'backend', backend_registry.list_builtin(), ignorecase=True) _auto_backend_sentinel = object() diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py index 98bdd9bd2793..541243379768 100644 --- a/lib/matplotlib/tests/test_backend_registry.py +++ b/lib/matplotlib/tests/test_backend_registry.py @@ -3,7 +3,7 @@ import pytest -from matplotlib.backends.registry import BackendFilter, backendRegistry +from matplotlib.backends.registry import BackendFilter, backend_registry def has_duplicates(seq: Sequence[Any]) -> bool: @@ -24,11 +24,11 @@ def has_duplicates(seq: Sequence[Any]) -> bool: ] ) def test_framework_to_backend(framework, expected): - assert backendRegistry.framework_to_backend(framework) == expected + assert backend_registry.framework_to_backend(framework) == expected def test_list_builtin(): - backends = backendRegistry.list_builtin() + backends = backend_registry.list_builtin() assert not has_duplicates(backends) # Compare using sets as order is not important assert set(backends) == set(( @@ -53,7 +53,7 @@ def test_list_builtin(): ] ) def test_list_builtin_with_filter(filter, expected): - backends = backendRegistry.list_builtin(filter) + backends = backend_registry.list_builtin(filter) assert not has_duplicates(backends) # Compare using sets as order is not important assert set(backends) == set(expected) diff --git a/lib/matplotlib/tests/test_matplotlib.py b/lib/matplotlib/tests/test_matplotlib.py index 6baef7c07e88..9cc68fefaca0 100644 --- a/lib/matplotlib/tests/test_matplotlib.py +++ b/lib/matplotlib/tests/test_matplotlib.py @@ -57,12 +57,12 @@ def parse(key): backends += [e.strip() for e in line.split(',') if e] return backends - from matplotlib.backends.registry import BackendFilter, backendRegistry + from matplotlib.backends.registry import BackendFilter, backend_registry assert (set(parse('- interactive backends:\n')) == - set(backendRegistry.list_builtin(BackendFilter.INTERACTIVE))) + set(backend_registry.list_builtin(BackendFilter.INTERACTIVE))) assert (set(parse('- non-interactive backends:\n')) == - set(backendRegistry.list_builtin(BackendFilter.NON_INTERACTIVE))) + set(backend_registry.list_builtin(BackendFilter.NON_INTERACTIVE))) def test_importable_with__OO(): From c20c0ac994709953b0bbfdadf786eac12929ef50 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 31 Jan 2024 11:22:28 +0000 Subject: [PATCH 03/17] Use class variables for immutable collections --- lib/matplotlib/backends/registry.py | 65 ++++++++++++++--------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py index d7c50b66f1d2..1fa11c555f57 100644 --- a/lib/matplotlib/backends/registry.py +++ b/lib/matplotlib/backends/registry.py @@ -13,48 +13,47 @@ class BackendRegistry: This is the single source of truth for available backends. """ - def __init__(self): - # Built-in backends are those which are included in the Matplotlib repo. - # A backend with name 'name' is located in the module - # f'matplotlib.backends.backend_{name.lower()}' - - # The capitalized forms are needed for ipython at present; this may - # change for later versions. - self._builtin_interactive = [ - "GTK3Agg", "GTK3Cairo", "GTK4Agg", "GTK4Cairo", - "MacOSX", - "nbAgg", - "QtAgg", "QtCairo", "Qt5Agg", "Qt5Cairo", - "TkAgg", "TkCairo", - "WebAgg", - "WX", "WXAgg", "WXCairo", - ] - self._builtin_not_interactive = [ - "agg", "cairo", "pdf", "pgf", "ps", "svg", "template", - ] - self._framework_to_backend_mapping = { - "qt": "qtagg", - "gtk3": "gtk3agg", - "gtk4": "gtk4agg", - "wx": "wxagg", - "tk": "tkagg", - "macosx": "macosx", - "headless": "agg", - } + # Built-in backends are those which are included in the Matplotlib repo. + # A backend with name 'name' is located in the module + # f'matplotlib.backends.backend_{name.lower()}' + + # The capitalized forms are needed for ipython at present; this may + # change for later versions. + _BUILTIN_INTERACTIVE = [ + "GTK3Agg", "GTK3Cairo", "GTK4Agg", "GTK4Cairo", + "MacOSX", + "nbAgg", + "QtAgg", "QtCairo", "Qt5Agg", "Qt5Cairo", + "TkAgg", "TkCairo", + "WebAgg", + "WX", "WXAgg", "WXCairo", + ] + _BUILTIN_NOT_INTERACTIVE = [ + "agg", "cairo", "pdf", "pgf", "ps", "svg", "template", + ] + _FRAMEWORK_TO_BACKEND_MAPPING = { + "qt": "qtagg", + "gtk3": "gtk3agg", + "gtk4": "gtk4agg", + "wx": "wxagg", + "tk": "tkagg", + "macosx": "macosx", + "headless": "agg", + } def framework_to_backend(self, interactive_framework): - return self._framework_to_backend_mapping.get(interactive_framework) + return self._FRAMEWORK_TO_BACKEND_MAPPING.get(interactive_framework) def list_builtin(self, filter_=None): if filter_ == BackendFilter.INTERACTIVE: - return self._builtin_interactive + return self._BUILTIN_INTERACTIVE elif filter_ == BackendFilter.INTERACTIVE_NON_WEB: return list(filter(lambda x: x.lower() not in ("webagg", "nbagg"), - self._builtin_interactive)) + self._BUILTIN_INTERACTIVE)) elif filter_ == BackendFilter.NON_INTERACTIVE: - return self._builtin_not_interactive + return self._BUILTIN_NOT_INTERACTIVE - return self._builtin_interactive + self._builtin_not_interactive + return self._BUILTIN_INTERACTIVE + self._BUILTIN_NOT_INTERACTIVE # Singleton From 9a952c1f663892f6f4b9e89832221c8b61674dc5 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 31 Jan 2024 11:33:39 +0000 Subject: [PATCH 04/17] Review comments --- lib/matplotlib/backend_bases.py | 2 +- lib/matplotlib/backends/registry.py | 6 +++--- lib/matplotlib/backends/registry.pyi | 2 +- lib/matplotlib/pyplot.py | 5 +++-- lib/matplotlib/tests/test_backend_registry.py | 4 ++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 586900529cb2..55d86973d7c8 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -106,7 +106,7 @@ def _safe_pyplot_import(): raise # No, something else went wrong, likely with the install... from matplotlib.backends.registry import backend_registry - backend = backend_registry.framework_to_backend(current_framework) + backend = backend_registry.backend_for_gui_framework(current_framework) if backend is None: raise KeyError(backend) diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py index 1fa11c555f57..74e275415266 100644 --- a/lib/matplotlib/backends/registry.py +++ b/lib/matplotlib/backends/registry.py @@ -31,7 +31,7 @@ class BackendRegistry: _BUILTIN_NOT_INTERACTIVE = [ "agg", "cairo", "pdf", "pgf", "ps", "svg", "template", ] - _FRAMEWORK_TO_BACKEND_MAPPING = { + _GUI_FRAMEWORK_TO_BACKEND_MAPPING = { "qt": "qtagg", "gtk3": "gtk3agg", "gtk4": "gtk4agg", @@ -41,8 +41,8 @@ class BackendRegistry: "headless": "agg", } - def framework_to_backend(self, interactive_framework): - return self._FRAMEWORK_TO_BACKEND_MAPPING.get(interactive_framework) + def backend_for_gui_framework(self, framework): + return self._GUI_FRAMEWORK_TO_BACKEND_MAPPING.get(framework) def list_builtin(self, filter_=None): if filter_ == BackendFilter.INTERACTIVE: diff --git a/lib/matplotlib/backends/registry.pyi b/lib/matplotlib/backends/registry.pyi index e847ca690629..e68d55717c20 100644 --- a/lib/matplotlib/backends/registry.pyi +++ b/lib/matplotlib/backends/registry.pyi @@ -9,7 +9,7 @@ class BackendFilter(Enum): class BackendRegistry: def __init__(self) -> None: ... - def framework_to_backend(self, interactive_framework: str) -> str | None: ... + def backend_for_gui_framework(self, interactive_framework: str) -> str | None: ... def list_builtin(self, filter_: BackendFilter | None) -> list[str]: ... diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 329de8b3d26f..0c08e25cfb83 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -304,7 +304,8 @@ def switch_backend(newbackend: str) -> None: current_framework = cbook._get_running_interactive_framework() if (current_framework and - (backend := backend_registry.framework_to_backend(current_framework))): + (backend := backend_registry.backend_for_gui_framework( + current_framework))): candidates = [backend] else: candidates = [] @@ -2505,7 +2506,7 @@ def polar(*args, **kwargs) -> list[Line2D]: # is compatible with the current running interactive framework. if (rcParams["backend_fallback"] and rcParams._get_backend_or_none() in ( # type: ignore - set(backend_registry.list_builtin(BackendFilter.INTERACTIVE_NON_WEB))) + backend_registry.list_builtin(BackendFilter.INTERACTIVE_NON_WEB)) and cbook._get_running_interactive_framework()): # type: ignore rcParams._set("backend", rcsetup._auto_backend_sentinel) # type: ignore diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py index 541243379768..123243dae924 100644 --- a/lib/matplotlib/tests/test_backend_registry.py +++ b/lib/matplotlib/tests/test_backend_registry.py @@ -23,8 +23,8 @@ def has_duplicates(seq: Sequence[Any]) -> bool: ('does not exist', None), ] ) -def test_framework_to_backend(framework, expected): - assert backend_registry.framework_to_backend(framework) == expected +def test_backend_for_gui_framework(framework, expected): + assert backend_registry.backend_for_gui_framework(framework) == expected def test_list_builtin(): From 5a7a7a2ef95e38c8cb42b0ee5e2f6102b4d257c3 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 2 Feb 2024 09:23:27 +0000 Subject: [PATCH 05/17] Update lib/matplotlib/backend_bases.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/backend_bases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 55d86973d7c8..68eae35f1596 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -108,7 +108,7 @@ def _safe_pyplot_import(): from matplotlib.backends.registry import backend_registry backend = backend_registry.backend_for_gui_framework(current_framework) if backend is None: - raise KeyError(backend) + raise RuntimeError(f"No suitable backend for the current GUI framework {current_framework!r}") rcParams["backend"] = mpl.rcParamsOrig["backend"] = backend import matplotlib.pyplot as plt # Now this should succeed. From 896b3e32ad882c9e8e694c31ad16b1cb85646348 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 2 Feb 2024 09:30:26 +0000 Subject: [PATCH 06/17] Linting/mypy fixes --- lib/matplotlib/backend_bases.py | 3 ++- lib/matplotlib/rcsetup.pyi | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 68eae35f1596..9c99b05966ed 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -108,7 +108,8 @@ def _safe_pyplot_import(): from matplotlib.backends.registry import backend_registry backend = backend_registry.backend_for_gui_framework(current_framework) if backend is None: - raise RuntimeError(f"No suitable backend for the current GUI framework {current_framework!r}") + raise RuntimeError("No suitable backend for the current GUI framework " + f"{current_framework!r}") rcParams["backend"] = mpl.rcParamsOrig["backend"] = backend import matplotlib.pyplot as plt # Now this should succeed. diff --git a/lib/matplotlib/rcsetup.pyi b/lib/matplotlib/rcsetup.pyi index 1538dac7510e..1d393e2797b0 100644 --- a/lib/matplotlib/rcsetup.pyi +++ b/lib/matplotlib/rcsetup.pyi @@ -8,6 +8,8 @@ interactive_bk: list[str] non_interactive_bk: list[str] all_backends: list[str] +def __dir__() -> list[str]: ... + _T = TypeVar("_T") def _listify_validator(s: Callable[[Any], _T]) -> Callable[[Any], list[_T]]: ... From de94746d4af2ef424c49b84ec9ba8f3c651a078b Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 9 Feb 2024 11:12:47 +0000 Subject: [PATCH 07/17] Remove INTERACTIVE_NON_WEB backend filter --- lib/matplotlib/backends/registry.py | 6 +----- lib/matplotlib/backends/registry.pyi | 1 - lib/matplotlib/pyplot.py | 3 ++- lib/matplotlib/tests/test_backend_registry.py | 3 --- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py index 74e275415266..94b976d51688 100644 --- a/lib/matplotlib/backends/registry.py +++ b/lib/matplotlib/backends/registry.py @@ -3,8 +3,7 @@ class BackendFilter(Enum): INTERACTIVE = 0 - INTERACTIVE_NON_WEB = 1 - NON_INTERACTIVE = 2 + NON_INTERACTIVE = 1 class BackendRegistry: @@ -47,9 +46,6 @@ def backend_for_gui_framework(self, framework): def list_builtin(self, filter_=None): if filter_ == BackendFilter.INTERACTIVE: return self._BUILTIN_INTERACTIVE - elif filter_ == BackendFilter.INTERACTIVE_NON_WEB: - return list(filter(lambda x: x.lower() not in ("webagg", "nbagg"), - self._BUILTIN_INTERACTIVE)) elif filter_ == BackendFilter.NON_INTERACTIVE: return self._BUILTIN_NOT_INTERACTIVE diff --git a/lib/matplotlib/backends/registry.pyi b/lib/matplotlib/backends/registry.pyi index e68d55717c20..ef3579bcd271 100644 --- a/lib/matplotlib/backends/registry.pyi +++ b/lib/matplotlib/backends/registry.pyi @@ -3,7 +3,6 @@ from enum import Enum class BackendFilter(Enum): INTERACTIVE: int - INTERACTIVE_NON_WEB: int NON_INTERACTIVE: int diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 0c08e25cfb83..b266a206e9c0 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2506,7 +2506,8 @@ def polar(*args, **kwargs) -> list[Line2D]: # is compatible with the current running interactive framework. if (rcParams["backend_fallback"] and rcParams._get_backend_or_none() in ( # type: ignore - backend_registry.list_builtin(BackendFilter.INTERACTIVE_NON_WEB)) + set(backend_registry.list_builtin(BackendFilter.INTERACTIVE)) - + {'WebAgg', 'nbAgg'}) and cbook._get_running_interactive_framework()): # type: ignore rcParams._set("backend", rcsetup._auto_backend_sentinel) # type: ignore diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py index 123243dae924..3c54b92b1662 100644 --- a/lib/matplotlib/tests/test_backend_registry.py +++ b/lib/matplotlib/tests/test_backend_registry.py @@ -45,9 +45,6 @@ def test_list_builtin(): ['GTK3Agg', 'GTK3Cairo', 'GTK4Agg', 'GTK4Cairo', 'MacOSX', 'nbAgg', 'QtAgg', 'QtCairo', 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'TkCairo', 'WebAgg', 'WX', 'WXAgg', 'WXCairo']), - (BackendFilter.INTERACTIVE_NON_WEB, - ['GTK3Agg', 'GTK3Cairo', 'GTK4Agg', 'GTK4Cairo', 'MacOSX', 'QtAgg', 'QtCairo', - 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'TkCairo', 'WX', 'WXAgg', 'WXCairo']), (BackendFilter.NON_INTERACTIVE, ['agg', 'cairo', 'pdf', 'pgf', 'ps', 'svg', 'template']), ] From f852721b0029adf7a1ae98b0e39e2292caac7f6f Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 14 Feb 2024 11:41:07 +0000 Subject: [PATCH 08/17] Small changes from review --- lib/matplotlib/pyplot.py | 2 +- lib/matplotlib/tests/test_backend_registry.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index b266a206e9c0..0e6fc6f8b7ca 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -305,7 +305,7 @@ def switch_backend(newbackend: str) -> None: if (current_framework and (backend := backend_registry.backend_for_gui_framework( - current_framework))): + current_framework))): candidates = [backend] else: candidates = [] diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py index 3c54b92b1662..ca1a667d5e36 100644 --- a/lib/matplotlib/tests/test_backend_registry.py +++ b/lib/matplotlib/tests/test_backend_registry.py @@ -31,11 +31,11 @@ def test_list_builtin(): backends = backend_registry.list_builtin() assert not has_duplicates(backends) # Compare using sets as order is not important - assert set(backends) == set(( + assert {*backends} == { 'GTK3Agg', 'GTK3Cairo', 'GTK4Agg', 'GTK4Cairo', 'MacOSX', 'nbAgg', 'QtAgg', 'QtCairo', 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'TkCairo', 'WebAgg', 'WX', 'WXAgg', 'WXCairo', 'agg', 'cairo', 'pdf', 'pgf', 'ps', 'svg', 'template', - )) + } @pytest.mark.parametrize( @@ -53,4 +53,4 @@ def test_list_builtin_with_filter(filter, expected): backends = backend_registry.list_builtin(filter) assert not has_duplicates(backends) # Compare using sets as order is not important - assert set(backends) == set(expected) + assert {*backends} == {*expected} From a0363a3dc0ed0029eeb017fcf85dfb7d926b85e9 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 14 Feb 2024 11:50:43 +0000 Subject: [PATCH 09/17] Use _api.caching_module_getattr for deprecated module-level attributes --- lib/matplotlib/rcsetup.py | 39 +++++++++---------- lib/matplotlib/tests/test_backend_registry.py | 11 ++++++ 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 6ef689241a6a..fc0bbe325649 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -39,28 +39,25 @@ BackendFilter.NON_INTERACTIVE) _deprecated_all_backends = backend_registry.list_builtin() -_deprecated_names_and_args = { - "interactive_bk": "matplotlib.backends.registry.BackendFilter.INTERACTIVE", - "non_interactive_bk": "matplotlib.backends.registry.BackendFilter.NON_INTERACTIVE", - "all_backends": "", -} - - -def __getattr__(name): - if name in _deprecated_names_and_args: - arg = _deprecated_names_and_args[name] - _api.warn_deprecated( - "3.9.0", - name=name, - alternative="``matplotlib.backends.registry.backend_registry" - f".list_builtin({arg})``", - ) - return globals()[f"_deprecated_{name}"] - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") - -def __dir__(): - return sorted(globals().keys() | _deprecated_names_and_args.keys()) +@_api.caching_module_getattr +class __getattr__: + interactive_bk = _api.deprecated( + "3.9", + alternative="``matplotlib.backends.registry.backend_registry.list_builtin" + "(matplotlib.backends.registry.BackendFilter.INTERACTIVE)``" + )(property(lambda self: _deprecated_interactive_bk)) + + non_interactive_bk = _api.deprecated( + "3.9", + alternative="``matplotlib.backends.registry.backend_registry.list_builtin" + "(matplotlib.backends.registry.BackendFilter.NON_INTERACTIVE)``" + )(property(lambda self: _deprecated_non_interactive_bk)) + + all_backends = _api.deprecated( + "3.9", + alternative="``matplotlib.backends.registry.backend_registry.list_builtin()``" + )(property(lambda self: _deprecated_all_backends)) class ValidateInStrings: diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py index ca1a667d5e36..f6217bd9e410 100644 --- a/lib/matplotlib/tests/test_backend_registry.py +++ b/lib/matplotlib/tests/test_backend_registry.py @@ -3,6 +3,7 @@ import pytest +import matplotlib as mpl from matplotlib.backends.registry import BackendFilter, backend_registry @@ -54,3 +55,13 @@ def test_list_builtin_with_filter(filter, expected): assert not has_duplicates(backends) # Compare using sets as order is not important assert {*backends} == {*expected} + + +def test_deprecated_rcsetup_attributes(): + match = "was deprecated in Matplotlib 3.9" + with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match): + mpl.rcsetup.interactive_bk + with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match): + mpl.rcsetup.non_interactive_bk + with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match): + mpl.rcsetup.all_backends From ff8b24bcf79c53bd4c11585d49cd56b062af5603 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 14 Feb 2024 12:18:28 +0000 Subject: [PATCH 10/17] Add docstrings --- lib/matplotlib/backends/registry.py | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py index 94b976d51688..8c0687c6f6dd 100644 --- a/lib/matplotlib/backends/registry.py +++ b/lib/matplotlib/backends/registry.py @@ -2,6 +2,11 @@ class BackendFilter(Enum): + """ + Filter used with :meth:`~.BackendRegistry.list_builtins` + + .. versionadded:: 3.9 + """ INTERACTIVE = 0 NON_INTERACTIVE = 1 @@ -11,6 +16,8 @@ class BackendRegistry: Registry of backends available within Matplotlib. This is the single source of truth for available backends. + + .. versionadded:: 3.9 """ # Built-in backends are those which are included in the Matplotlib repo. # A backend with name 'name' is located in the module @@ -41,9 +48,36 @@ class BackendRegistry: } def backend_for_gui_framework(self, framework): + """ + Return the name of the backend corresponding to the specified GUI framework. + + Parameters + ---------- + framework : str + GUI framework such as "qt". + + Returns + ------- + str + Backend name. + """ return self._GUI_FRAMEWORK_TO_BACKEND_MAPPING.get(framework) def list_builtin(self, filter_=None): + """ + Return list of backends that are built into Matplotlib. + + Parameters + ---------- + filter_ : `~.BackendFilter`, optional + Filter to apply to returned backends. For example, to return only + non-interactive backends use `.BackendFilter.NON_INTERACTIVE`. + + Returns + ------- + list of str + Backend names. + """ if filter_ == BackendFilter.INTERACTIVE: return self._BUILTIN_INTERACTIVE elif filter_ == BackendFilter.NON_INTERACTIVE: From 67bec5be504c347e3241dac37956c99f3d1c67b3 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 14 Feb 2024 12:34:08 +0000 Subject: [PATCH 11/17] Add api changes and what new docs --- doc/api/backend_registry_api.rst | 8 ++++++++ doc/api/index_backend_api.rst | 1 + doc/api/next_api_changes/deprecations/27719-IT.rst | 12 ++++++++++++ doc/users/next_whats_new/backend_registry.rst | 5 +++++ 4 files changed, 26 insertions(+) create mode 100644 doc/api/backend_registry_api.rst create mode 100644 doc/api/next_api_changes/deprecations/27719-IT.rst create mode 100644 doc/users/next_whats_new/backend_registry.rst diff --git a/doc/api/backend_registry_api.rst b/doc/api/backend_registry_api.rst new file mode 100644 index 000000000000..ca184c67d0a2 --- /dev/null +++ b/doc/api/backend_registry_api.rst @@ -0,0 +1,8 @@ +******************************** +``matplotlib.backends.registry`` +******************************** + +.. automodule:: matplotlib.backends.registry + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/index_backend_api.rst b/doc/api/index_backend_api.rst index 6012f71c52a4..66009d86825d 100644 --- a/doc/api/index_backend_api.rst +++ b/doc/api/index_backend_api.rst @@ -17,6 +17,7 @@ backend_pdf_api.rst backend_pgf_api.rst backend_ps_api.rst + backend_registry_api.rst backend_qt_api.rst backend_svg_api.rst backend_tk_api.rst diff --git a/doc/api/next_api_changes/deprecations/27719-IT.rst b/doc/api/next_api_changes/deprecations/27719-IT.rst new file mode 100644 index 000000000000..4ae0c71a8098 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/27719-IT.rst @@ -0,0 +1,12 @@ +`~.rcsetup.interactive_bk`, `~.rcsetup.non_interactive_bk` and `~.rcsetup.all_backends` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +... are deprecated and replaced by +:meth:`matplotlib.backends.registry.backend_registry.list_builtin` +with the following arguments + +- `matplotlib.backends.registry.BackendFilter.INTERACTIVE` +- `matplotlib.backends.registry.BackendFilter.NON_INTERACTIVE` +- ``None`` + +respectively. diff --git a/doc/users/next_whats_new/backend_registry.rst b/doc/users/next_whats_new/backend_registry.rst new file mode 100644 index 000000000000..59e19403ff19 --- /dev/null +++ b/doc/users/next_whats_new/backend_registry.rst @@ -0,0 +1,5 @@ +BackendRegistry +~~~~~~~~~~~~~~~ + +New `~/.BackendRegistry` class is the single source of truth for available +backends. The singleton instance is `matplotlib.backends.registry.backend_registry`. From c77dc59bc778c0b048e96c27204a9e27e27ab737 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 14 Feb 2024 14:02:14 +0000 Subject: [PATCH 12/17] Fix docs --- doc/api/next_api_changes/deprecations/27719-IT.rst | 4 ++-- doc/users/next_whats_new/backend_registry.rst | 5 +++-- lib/matplotlib/backends/registry.py | 5 ++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/doc/api/next_api_changes/deprecations/27719-IT.rst b/doc/api/next_api_changes/deprecations/27719-IT.rst index 4ae0c71a8098..952403f2cd0e 100644 --- a/doc/api/next_api_changes/deprecations/27719-IT.rst +++ b/doc/api/next_api_changes/deprecations/27719-IT.rst @@ -1,8 +1,8 @@ -`~.rcsetup.interactive_bk`, `~.rcsetup.non_interactive_bk` and `~.rcsetup.all_backends` +``rcsetup.interactive_bk``, ``rcsetup.non_interactive_bk`` and ``rcsetup.all_backends`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ... are deprecated and replaced by -:meth:`matplotlib.backends.registry.backend_registry.list_builtin` +:meth:`matplotlib.backends.registry.BackendRegistry.list_builtin` with the following arguments - `matplotlib.backends.registry.BackendFilter.INTERACTIVE` diff --git a/doc/users/next_whats_new/backend_registry.rst b/doc/users/next_whats_new/backend_registry.rst index 59e19403ff19..3bdac58d6124 100644 --- a/doc/users/next_whats_new/backend_registry.rst +++ b/doc/users/next_whats_new/backend_registry.rst @@ -1,5 +1,6 @@ BackendRegistry ~~~~~~~~~~~~~~~ -New `~/.BackendRegistry` class is the single source of truth for available -backends. The singleton instance is `matplotlib.backends.registry.backend_registry`. +New :class:`~matplotlib.backends.registry.BackendRegistry` class is the single +source of truth for available backends. The singleton instance is +``matplotlib.backends.registry.backend_registry``. diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py index 8c0687c6f6dd..4b8b7bee7e91 100644 --- a/lib/matplotlib/backends/registry.py +++ b/lib/matplotlib/backends/registry.py @@ -3,7 +3,7 @@ class BackendFilter(Enum): """ - Filter used with :meth:`~.BackendRegistry.list_builtins` + Filter used with :meth:`~matplotlib.backends.registry.BackendRegistry.list_builtin` .. versionadded:: 3.9 """ @@ -17,6 +17,9 @@ class BackendRegistry: This is the single source of truth for available backends. + All use of ``BackendRegistry`` should be via the singleton instance + ``backend_registry``. + .. versionadded:: 3.9 """ # Built-in backends are those which are included in the Matplotlib repo. From c2fa794d86869ba71abaf495d042fa34a78704eb Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Thu, 15 Feb 2024 09:06:51 +0000 Subject: [PATCH 13/17] Inline the deprecation function calls --- lib/matplotlib/rcsetup.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index fc0bbe325649..c955f9006676 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -33,31 +33,30 @@ from cycler import Cycler, cycler as ccycler -# Deprecation of module-level attributes using PEP 562 -_deprecated_interactive_bk = backend_registry.list_builtin(BackendFilter.INTERACTIVE) -_deprecated_non_interactive_bk = backend_registry.list_builtin( - BackendFilter.NON_INTERACTIVE) -_deprecated_all_backends = backend_registry.list_builtin() - - @_api.caching_module_getattr class __getattr__: - interactive_bk = _api.deprecated( + @_api.deprecated( "3.9", alternative="``matplotlib.backends.registry.backend_registry.list_builtin" - "(matplotlib.backends.registry.BackendFilter.INTERACTIVE)``" - )(property(lambda self: _deprecated_interactive_bk)) + "(matplotlib.backends.registry.BackendFilter.INTERACTIVE)``") + @property + def interactive_bk(self): + return backend_registry.list_builtin(BackendFilter.INTERACTIVE) - non_interactive_bk = _api.deprecated( + @_api.deprecated( "3.9", alternative="``matplotlib.backends.registry.backend_registry.list_builtin" - "(matplotlib.backends.registry.BackendFilter.NON_INTERACTIVE)``" - )(property(lambda self: _deprecated_non_interactive_bk)) + "(matplotlib.backends.registry.BackendFilter.NON_INTERACTIVE)``") + @property + def non_interactive_bk(self): + return backend_registry.list_builtin(BackendFilter.NON_INTERACTIVE) - all_backends = _api.deprecated( + @_api.deprecated( "3.9", - alternative="``matplotlib.backends.registry.backend_registry.list_builtin()``" - )(property(lambda self: _deprecated_all_backends)) + alternative="``matplotlib.backends.registry.backend_registry.list_builtin()``") + @property + def all_backends(self): + return backend_registry.list_builtin() class ValidateInStrings: From a5ae8dfa34dc224e7acb8aeb08a6718b62cb702e Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Thu, 15 Feb 2024 09:35:34 +0000 Subject: [PATCH 14/17] Import BackendFilter and backend_registry into matplotlib.backends.__init__.py --- doc/api/next_api_changes/deprecations/27719-IT.rst | 7 +++---- doc/users/next_whats_new/backend_registry.rst | 2 +- lib/matplotlib/backend_bases.py | 2 +- lib/matplotlib/backends/__init__.py | 2 ++ lib/matplotlib/backends/registry.py | 2 +- lib/matplotlib/pyplot.py | 2 +- lib/matplotlib/rcsetup.py | 12 ++++++------ lib/matplotlib/tests/test_backend_registry.py | 2 +- lib/matplotlib/tests/test_matplotlib.py | 2 +- 9 files changed, 17 insertions(+), 16 deletions(-) diff --git a/doc/api/next_api_changes/deprecations/27719-IT.rst b/doc/api/next_api_changes/deprecations/27719-IT.rst index 952403f2cd0e..c41e9d2c396f 100644 --- a/doc/api/next_api_changes/deprecations/27719-IT.rst +++ b/doc/api/next_api_changes/deprecations/27719-IT.rst @@ -1,12 +1,11 @@ ``rcsetup.interactive_bk``, ``rcsetup.non_interactive_bk`` and ``rcsetup.all_backends`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... are deprecated and replaced by -:meth:`matplotlib.backends.registry.BackendRegistry.list_builtin` +... are deprecated and replaced by ``matplotlib.backends.backend_registry.list_builtin`` with the following arguments -- `matplotlib.backends.registry.BackendFilter.INTERACTIVE` -- `matplotlib.backends.registry.BackendFilter.NON_INTERACTIVE` +- ``matplotlib.backends.BackendFilter.INTERACTIVE`` +- ``matplotlib.backends.BackendFilter.NON_INTERACTIVE`` - ``None`` respectively. diff --git a/doc/users/next_whats_new/backend_registry.rst b/doc/users/next_whats_new/backend_registry.rst index 3bdac58d6124..61b65a9d6470 100644 --- a/doc/users/next_whats_new/backend_registry.rst +++ b/doc/users/next_whats_new/backend_registry.rst @@ -3,4 +3,4 @@ BackendRegistry New :class:`~matplotlib.backends.registry.BackendRegistry` class is the single source of truth for available backends. The singleton instance is -``matplotlib.backends.registry.backend_registry``. +``matplotlib.backends.backend_registry``. diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 9c99b05966ed..8e789c908152 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -105,7 +105,7 @@ def _safe_pyplot_import(): if current_framework is None: raise # No, something else went wrong, likely with the install... - from matplotlib.backends.registry import backend_registry + from matplotlib.backends import backend_registry backend = backend_registry.backend_for_gui_framework(current_framework) if backend is None: raise RuntimeError("No suitable backend for the current GUI framework " diff --git a/lib/matplotlib/backends/__init__.py b/lib/matplotlib/backends/__init__.py index 3e687f85b0be..cf0f682d5d4b 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -1,3 +1,5 @@ +from .registry import BackendFilter, backend_registry # noqa: F401 + # NOTE: plt.switch_backend() (called at import time) will add a "backend" # attribute here for backcompat. _QT_FORCE_QT5_BINDING = False diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py index 4b8b7bee7e91..484d6ed5f26d 100644 --- a/lib/matplotlib/backends/registry.py +++ b/lib/matplotlib/backends/registry.py @@ -18,7 +18,7 @@ class BackendRegistry: This is the single source of truth for available backends. All use of ``BackendRegistry`` should be via the singleton instance - ``backend_registry``. + ``backend_registry`` which can be imported from ``matplotlib.backends``. .. versionadded:: 3.9 """ diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 0e6fc6f8b7ca..f9a4ddf701a9 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -69,7 +69,7 @@ from matplotlib.artist import Artist from matplotlib.axes import Axes from matplotlib.axes import Subplot # noqa: F401 -from matplotlib.backends.registry import BackendFilter, backendRegistry +from matplotlib.backends import BackendFilter, backend_registry from matplotlib.projections import PolarAxes from matplotlib import mlab # for detrend_none, window_hanning from matplotlib.scale import get_scale_names # noqa: F401 diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index c955f9006676..6abc8372222d 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -23,7 +23,7 @@ import numpy as np from matplotlib import _api, cbook -from matplotlib.backends.registry import BackendFilter, backend_registry +from matplotlib.backends import BackendFilter, backend_registry from matplotlib.cbook import ls_mapper from matplotlib.colors import Colormap, is_color_like from matplotlib._fontconfig_pattern import parse_fontconfig_pattern @@ -37,23 +37,23 @@ class __getattr__: @_api.deprecated( "3.9", - alternative="``matplotlib.backends.registry.backend_registry.list_builtin" - "(matplotlib.backends.registry.BackendFilter.INTERACTIVE)``") + alternative="``matplotlib.backends.backend_registry.list_builtin" + "(matplotlib.backends.BackendFilter.INTERACTIVE)``") @property def interactive_bk(self): return backend_registry.list_builtin(BackendFilter.INTERACTIVE) @_api.deprecated( "3.9", - alternative="``matplotlib.backends.registry.backend_registry.list_builtin" - "(matplotlib.backends.registry.BackendFilter.NON_INTERACTIVE)``") + alternative="``matplotlib.backends.backend_registry.list_builtin" + "(matplotlib.backends.BackendFilter.NON_INTERACTIVE)``") @property def non_interactive_bk(self): return backend_registry.list_builtin(BackendFilter.NON_INTERACTIVE) @_api.deprecated( "3.9", - alternative="``matplotlib.backends.registry.backend_registry.list_builtin()``") + alternative="``matplotlib.backends.backend_registry.list_builtin()``") @property def all_backends(self): return backend_registry.list_builtin() diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py index f6217bd9e410..aed258f36413 100644 --- a/lib/matplotlib/tests/test_backend_registry.py +++ b/lib/matplotlib/tests/test_backend_registry.py @@ -4,7 +4,7 @@ import pytest import matplotlib as mpl -from matplotlib.backends.registry import BackendFilter, backend_registry +from matplotlib.backends import BackendFilter, backend_registry def has_duplicates(seq: Sequence[Any]) -> bool: diff --git a/lib/matplotlib/tests/test_matplotlib.py b/lib/matplotlib/tests/test_matplotlib.py index 9cc68fefaca0..a2f467ac48de 100644 --- a/lib/matplotlib/tests/test_matplotlib.py +++ b/lib/matplotlib/tests/test_matplotlib.py @@ -57,7 +57,7 @@ def parse(key): backends += [e.strip() for e in line.split(',') if e] return backends - from matplotlib.backends.registry import BackendFilter, backend_registry + from matplotlib.backends import BackendFilter, backend_registry assert (set(parse('- interactive backends:\n')) == set(backend_registry.list_builtin(BackendFilter.INTERACTIVE))) From 7812db71aaaa2337626d4e63d6f5046ca9937d99 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Tue, 27 Feb 2024 11:32:48 +0000 Subject: [PATCH 15/17] Mypy fixes --- lib/matplotlib/pyplot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index f9a4ddf701a9..778a9e132d43 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2505,11 +2505,11 @@ def polar(*args, **kwargs) -> list[Line2D]: # 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 + 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()): # type: ignore - rcParams._set("backend", rcsetup._auto_backend_sentinel) # type: ignore + and cbook._get_running_interactive_framework()): + rcParams._set("backend", rcsetup._auto_backend_sentinel) # fmt: on From 69ba4498cfe28f91ad8e18599a75603429f5d8d9 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 4 Mar 2024 20:18:26 +0000 Subject: [PATCH 16/17] Remove unused _safe_pyplot_import --- lib/matplotlib/backend_bases.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 8e789c908152..a1e7eb4f3ffc 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -93,29 +93,6 @@ } -def _safe_pyplot_import(): - """ - Import and return ``pyplot``, correctly setting the backend if one is - already forced. - """ - try: - import matplotlib.pyplot as plt - except ImportError: # Likely due to a framework mismatch. - current_framework = cbook._get_running_interactive_framework() - if current_framework is None: - raise # No, something else went wrong, likely with the install... - - from matplotlib.backends import backend_registry - backend = backend_registry.backend_for_gui_framework(current_framework) - if backend is None: - raise RuntimeError("No suitable backend for the current GUI framework " - f"{current_framework!r}") - - rcParams["backend"] = mpl.rcParamsOrig["backend"] = backend - import matplotlib.pyplot as plt # Now this should succeed. - return plt - - def register_backend(format, backend, description=None): """ Register a backend for saving to a given file format. From 64aa6d7fb67192f06542e36cd34d5ed13e218c64 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 8 Mar 2024 12:12:13 +0000 Subject: [PATCH 17/17] Remove unneeded type annotations --- lib/matplotlib/backends/registry.pyi | 1 - lib/matplotlib/rcsetup.pyi | 2 -- 2 files changed, 3 deletions(-) diff --git a/lib/matplotlib/backends/registry.pyi b/lib/matplotlib/backends/registry.pyi index ef3579bcd271..e48531be471d 100644 --- a/lib/matplotlib/backends/registry.pyi +++ b/lib/matplotlib/backends/registry.pyi @@ -7,7 +7,6 @@ class BackendFilter(Enum): class BackendRegistry: - def __init__(self) -> None: ... def backend_for_gui_framework(self, interactive_framework: str) -> str | None: ... def list_builtin(self, filter_: BackendFilter | None) -> list[str]: ... diff --git a/lib/matplotlib/rcsetup.pyi b/lib/matplotlib/rcsetup.pyi index 1d393e2797b0..1538dac7510e 100644 --- a/lib/matplotlib/rcsetup.pyi +++ b/lib/matplotlib/rcsetup.pyi @@ -8,8 +8,6 @@ interactive_bk: list[str] non_interactive_bk: list[str] all_backends: list[str] -def __dir__() -> list[str]: ... - _T = TypeVar("_T") def _listify_validator(s: Callable[[Any], _T]) -> Callable[[Any], list[_T]]: ...