-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add BackendRegistry singleton class #27719
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
bffe0be
Add BackendRegistry singleton class
ianthomas23 acb7bf5
Use backend_registry for name of singleton instance
ianthomas23 c20c0ac
Use class variables for immutable collections
ianthomas23 9a952c1
Review comments
ianthomas23 5a7a7a2
Update lib/matplotlib/backend_bases.py
ianthomas23 896b3e3
Linting/mypy fixes
ianthomas23 de94746
Remove INTERACTIVE_NON_WEB backend filter
ianthomas23 f852721
Small changes from review
ianthomas23 a0363a3
Use _api.caching_module_getattr for deprecated module-level attributes
ianthomas23 ff8b24b
Add docstrings
ianthomas23 67bec5b
Add api changes and what new docs
ianthomas23 c77dc59
Fix docs
ianthomas23 c2fa794
Inline the deprecation function calls
ianthomas23 a5ae8df
Import BackendFilter and backend_registry into
ianthomas23 7812db7
Mypy fixes
ianthomas23 69ba449
Remove unused _safe_pyplot_import
ianthomas23 64aa6d7
Remove unneeded type annotations
ianthomas23 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
******************************** | ||
``matplotlib.backends.registry`` | ||
******************************** | ||
|
||
.. automodule:: matplotlib.backends.registry | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
``rcsetup.interactive_bk``, ``rcsetup.non_interactive_bk`` and ``rcsetup.all_backends`` | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
... are deprecated and replaced by ``matplotlib.backends.backend_registry.list_builtin`` | ||
with the following arguments | ||
|
||
- ``matplotlib.backends.BackendFilter.INTERACTIVE`` | ||
- ``matplotlib.backends.BackendFilter.NON_INTERACTIVE`` | ||
- ``None`` | ||
|
||
respectively. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
BackendRegistry | ||
~~~~~~~~~~~~~~~ | ||
|
||
New :class:`~matplotlib.backends.registry.BackendRegistry` class is the single | ||
source of truth for available backends. The singleton instance is | ||
``matplotlib.backends.backend_registry``. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
from enum import Enum | ||
|
||
|
||
class BackendFilter(Enum): | ||
""" | ||
Filter used with :meth:`~matplotlib.backends.registry.BackendRegistry.list_builtin` | ||
|
||
.. versionadded:: 3.9 | ||
""" | ||
INTERACTIVE = 0 | ||
NON_INTERACTIVE = 1 | ||
|
||
|
||
class BackendRegistry: | ||
A93C """ | ||
Registry of backends available within Matplotlib. | ||
|
||
This is the single source of truth for available backends. | ||
|
||
All use of ``BackendRegistry`` should be via the singleton instance | ||
``backend_registry`` which can be imported from ``matplotlib.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 | ||
# 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", | ||
] | ||
_GUI_FRAMEWORK_TO_BACKEND_MAPPING = { | ||
"qt": "qtagg", | ||
"gtk3": "gtk3agg", | ||
"gtk4": "gtk4agg", | ||
"wx": "wxagg", | ||
"tk": "tkagg", | ||
"macosx": "macosx", | ||
"headless": "agg", | ||
} | ||
|
||
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: | ||
return self._BUILTIN_NOT_INTERACTIVE | ||
|
||
return self._BUILTIN_INTERACTIVE + self._BUILTIN_NOT_INTERACTIVE | ||
|
||
|
||
# Singleton | ||
backend_registry = BackendRegistry() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from enum import Enum | ||
|
||
|
||
class BackendFilter(Enum): | ||
INTERACTIVE: int | ||
NON_INTERACTIVE: int | ||
|
||
|
||
class BackendRegistry: | ||
def backend_for_gui_framework(self, interactive_framework: str) -> str | None: ... | ||
def list_builtin(self, filter_: BackendFilter | None) -> list[str]: ... | ||
|
||
|
||
backend_registry: BackendRegistry |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
from collections.abc import Sequence | ||
from typing import Any | ||
|
||
import pytest | ||
|
||
import matplotlib as mpl | ||
from matplotlib.backends import BackendFilter, backend_registry | ||
|
||
|
||
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_backend_for_gui_framework(framework, expected): | ||
assert backend_registry.backend_for_gui_framework(framework) == expected | ||
|
||
|
||
def test_list_builtin(): | ||
backends = backend_registry.list_builtin() | ||
assert not has_duplicates(backends) | ||
# Compare using sets as order is not important | ||
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( | ||
'filter,expected', | ||
[ | ||
(BackendFilter.INTERACTIVE, | ||
['GTK3Agg', 'GTK3Cairo', 'GTK4Agg', 'GTK4Cairo', 'MacOSX', 'nbAgg', 'QtAgg', | ||
'QtCairo', 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'TkCairo', 'WebAgg', 'WX', 'WXAgg', | ||
'WXCairo']), | ||
(BackendFilter.NON_INTERACTIVE, | ||
['agg', 'cairo', 'pdf', 'pgf', 'ps', 'svg', 'template']), | ||
] | ||
) | ||
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 {*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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now we have
matplotlib.backends.registry.backend_registry
, which seems a bit redundant. Would it make sense to move this to_registry.py
(to keep files small), and then import intomatplotlib/backends/__init__.py
so we havematplotlib.backends.registry
as the instance?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not. There is value in matching the the singleton instance name
backend_registry
to the class nameBackendRegistry
. Also the internal usage is frommatplotlib.backends.registry import backend_registry
, so the name is free standing. Onlyregistry
would be quite ambiguous (one could change to fully qualified usages only, but 🤷 ). And finally, the backend registry is used really rarely so that verbosity is not much of an issue.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I lean towards the explicit and verbose approach, but maybe there is some middle ground. If I keep the files named as they are but import into
matplotlib.backends.__init__
usingfrom .registry import BackendFilter, backend_registry
then the standard usage becomesrather than
removing some verbosity.
This has the added advantage that you cannot import the
BackendRegistry
class in the same way which is good as nobody should need the class, only the singleton instance.I have implemented this in commit 8f10a01.