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]]: ... pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy