Spaces:
Running
Running
| from enum import Enum | |
| import importlib | |
| class BackendFilter(Enum): | |
| """ | |
| Filter used with :meth:`~matplotlib.backends.registry.BackendRegistry.list_builtin` | |
| .. versionadded:: 3.9 | |
| """ | |
| INTERACTIVE = 0 | |
| NON_INTERACTIVE = 1 | |
| class BackendRegistry: | |
| """ | |
| 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``. | |
| Each backend has a name, a module name containing the backend code, and an | |
| optional GUI framework that must be running if the backend is interactive. | |
| There are three sources of backends: built-in (source code is within the | |
| Matplotlib repository), explicit ``module://some.backend`` syntax (backend is | |
| obtained by loading the module), or via an entry point (self-registering | |
| backend in an external package). | |
| .. versionadded:: 3.9 | |
| """ | |
| # Mapping of built-in backend name to GUI framework, or "headless" for no | |
| # GUI framework. 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()}" | |
| _BUILTIN_BACKEND_TO_GUI_FRAMEWORK = { | |
| "gtk3agg": "gtk3", | |
| "gtk3cairo": "gtk3", | |
| "gtk4agg": "gtk4", | |
| "gtk4cairo": "gtk4", | |
| "macosx": "macosx", | |
| "nbagg": "nbagg", | |
| "notebook": "nbagg", | |
| "qtagg": "qt", | |
| "qtcairo": "qt", | |
| "qt5agg": "qt5", | |
| "qt5cairo": "qt5", | |
| "tkagg": "tk", | |
| "tkcairo": "tk", | |
| "webagg": "webagg", | |
| "wx": "wx", | |
| "wxagg": "wx", | |
| "wxcairo": "wx", | |
| "agg": "headless", | |
| "cairo": "headless", | |
| "pdf": "headless", | |
| "pgf": "headless", | |
| "ps": "headless", | |
| "svg": "headless", | |
| "template": "headless", | |
| } | |
| # Reverse mapping of gui framework to preferred built-in backend. | |
| _GUI_FRAMEWORK_TO_BACKEND = { | |
| "gtk3": "gtk3agg", | |
| "gtk4": "gtk4agg", | |
| "headless": "agg", | |
| "macosx": "macosx", | |
| "qt": "qtagg", | |
| "qt5": "qt5agg", | |
| "qt6": "qtagg", | |
| "tk": "tkagg", | |
| "wx": "wxagg", | |
| } | |
| def __init__(self): | |
| # Only load entry points when first needed. | |
| self._loaded_entry_points = False | |
| # Mapping of non-built-in backend to GUI framework, added dynamically from | |
| # entry points and from matplotlib.use("module://some.backend") format. | |
| # New entries have an "unknown" GUI framework that is determined when first | |
| # needed by calling _get_gui_framework_by_loading. | |
| self._backend_to_gui_framework = {} | |
| # Mapping of backend name to module name, where different from | |
| # f"matplotlib.backends.backend_{backend_name.lower()}". These are either | |
| # hardcoded for backward compatibility, or loaded from entry points or | |
| # "module://some.backend" syntax. | |
| self._name_to_module = { | |
| "notebook": "nbagg", | |
| } | |
| def _backend_module_name(self, backend): | |
| if backend.startswith("module://"): | |
| return backend[9:] | |
| # Return name of module containing the specified backend. | |
| # Does not check if the backend is valid, use is_valid_backend for that. | |
| backend = backend.lower() | |
| # Check if have specific name to module mapping. | |
| backend = self._name_to_module.get(backend, backend) | |
| return (backend[9:] if backend.startswith("module://") | |
| else f"matplotlib.backends.backend_{backend}") | |
| def _clear(self): | |
| # Clear all dynamically-added data, used for testing only. | |
| self.__init__() | |
| def _ensure_entry_points_loaded(self): | |
| # Load entry points, if they have not already been loaded. | |
| if not self._loaded_entry_points: | |
| entries = self._read_entry_points() | |
| self._validate_and_store_entry_points(entries) | |
| self._loaded_entry_points = True | |
| def _get_gui_framework_by_loading(self, backend): | |
| # Determine GUI framework for a backend by loading its module and reading the | |
| # FigureCanvas.required_interactive_framework attribute. | |
| # Returns "headless" if there is no GUI framework. | |
| module = self.load_backend_module(backend) | |
| canvas_class = module.FigureCanvas | |
| return canvas_class.required_interactive_framework or "headless" | |
| def _read_entry_points(self): | |
| # Read entry points of modules that self-advertise as Matplotlib backends. | |
| # Expects entry points like this one from matplotlib-inline (in pyproject.toml | |
| # format): | |
| # [project.entry-points."matplotlib.backend"] | |
| # inline = "matplotlib_inline.backend_inline" | |
| import importlib.metadata as im | |
| entry_points = im.entry_points(group="matplotlib.backend") | |
| entries = [(entry.name, entry.value) for entry in entry_points] | |
| # For backward compatibility, if matplotlib-inline and/or ipympl are installed | |
| # but too old to include entry points, create them. Do not import ipympl | |
| # directly as this calls matplotlib.use() whilst in this function. | |
| def backward_compatible_entry_points( | |
| entries, module_name, threshold_version, names, target): | |
| from matplotlib import _parse_to_version_info | |
| try: | |
| module_version = im.version(module_name) | |
| if _parse_to_version_info(module_version) < threshold_version: | |
| for name in names: | |
| entries.append((name, target)) | |
| except im.PackageNotFoundError: | |
| pass | |
| names = [entry[0] for entry in entries] | |
| if "inline" not in names: | |
| backward_compatible_entry_points( | |
| entries, "matplotlib_inline", (0, 1, 7), ["inline"], | |
| "matplotlib_inline.backend_inline") | |
| if "ipympl" not in names: | |
| backward_compatible_entry_points( | |
| entries, "ipympl", (0, 9, 4), ["ipympl", "widget"], | |
| "ipympl.backend_nbagg") | |
| return entries | |
| def _validate_and_store_entry_points(self, entries): | |
| # Validate and store entry points so that they can be used via matplotlib.use() | |
| # in the normal manner. Entry point names cannot be of module:// format, cannot | |
| # shadow a built-in backend name, and there cannot be multiple entry points | |
| # with the same name but different modules. Multiple entry points with the same | |
| # name and value are permitted (it can sometimes happen outside of our control, | |
| # see https://github.com/matplotlib/matplotlib/issues/28367). | |
| for name, module in set(entries): | |
| name = name.lower() | |
| if name.startswith("module://"): | |
| raise RuntimeError( | |
| f"Entry point name '{name}' cannot start with 'module://'") | |
| if name in self._BUILTIN_BACKEND_TO_GUI_FRAMEWORK: | |
| raise RuntimeError(f"Entry point name '{name}' is a built-in backend") | |
| if name in self._backend_to_gui_framework: | |
| raise RuntimeError(f"Entry point name '{name}' duplicated") | |
| self._name_to_module[name] = "module://" + module | |
| # Do not yet know backend GUI framework, determine it only when necessary. | |
| self._backend_to_gui_framework[name] = "unknown" | |
| 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 or None | |
| Backend name or None if GUI framework not recognised. | |
| """ | |
| return self._GUI_FRAMEWORK_TO_BACKEND.get(framework.lower()) | |
| def is_valid_backend(self, backend): | |
| """ | |
| Return True if the backend name is valid, False otherwise. | |
| A backend name is valid if it is one of the built-in backends or has been | |
| dynamically added via an entry point. Those beginning with ``module://`` are | |
| always considered valid and are added to the current list of all backends | |
| within this function. | |
| Even if a name is valid, it may not be importable or usable. This can only be | |
| determined by loading and using the backend module. | |
| Parameters | |
| ---------- | |
| backend : str | |
| Name of backend. | |
| Returns | |
| ------- | |
| bool | |
| True if backend is valid, False otherwise. | |
| """ | |
| if not backend.startswith("module://"): | |
| backend = backend.lower() | |
| # For backward compatibility, convert ipympl and matplotlib-inline long | |
| # module:// names to their shortened forms. | |
| backwards_compat = { | |
| "module://ipympl.backend_nbagg": "widget", | |
| "module://matplotlib_inline.backend_inline": "inline", | |
| } | |
| backend = backwards_compat.get(backend, backend) | |
| if (backend in self._BUILTIN_BACKEND_TO_GUI_FRAMEWORK or | |
| backend in self._backend_to_gui_framework): | |
| return True | |
| if backend.startswith("module://"): | |
| self._backend_to_gui_framework[backend] = "unknown" | |
| return True | |
| # Only load entry points if really need to and not already done so. | |
| self._ensure_entry_points_loaded() | |
| if backend in self._backend_to_gui_framework: | |
| return True | |
| return False | |
| def list_all(self): | |
| """ | |
| Return list of all known backends. | |
| These include built-in backends and those obtained at runtime either from entry | |
| points or explicit ``module://some.backend`` syntax. | |
| Entry points will be loaded if they haven't been already. | |
| Returns | |
| ------- | |
| list of str | |
| Backend names. | |
| """ | |
| self._ensure_entry_points_loaded() | |
| return [*self.list_builtin(), *self._backend_to_gui_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 [k for k, v in self._BUILTIN_BACKEND_TO_GUI_FRAMEWORK.items() | |
| if v != "headless"] | |
| elif filter_ == BackendFilter.NON_INTERACTIVE: | |
| return [k for k, v in self._BUILTIN_BACKEND_TO_GUI_FRAMEWORK.items() | |
| if v == "headless"] | |
| return [*self._BUILTIN_BACKEND_TO_GUI_FRAMEWORK] | |
| def list_gui_frameworks(self): | |
| """ | |
| Return list of GUI frameworks used by Matplotlib backends. | |
| Returns | |
| ------- | |
| list of str | |
| GUI framework names. | |
| """ | |
| return [k for k in self._GUI_FRAMEWORK_TO_BACKEND if k != "headless"] | |
| def load_backend_module(self, backend): | |
| """ | |
| Load and return the module containing the specified backend. | |
| Parameters | |
| ---------- | |
| backend : str | |
| Name of backend to load. | |
| Returns | |
| ------- | |
| Module | |
| Module containing backend. | |
| """ | |
| module_name = self._backend_module_name(backend) | |
| return importlib.import_module(module_name) | |
| def resolve_backend(self, backend): | |
| """ | |
| Return the backend and GUI framework for the specified backend name. | |
| If the GUI framework is not yet known then it will be determined by loading the | |
| backend module and checking the ``FigureCanvas.required_interactive_framework`` | |
| attribute. | |
| This function only loads entry points if they have not already been loaded and | |
| the backend is not built-in and not of ``module://some.backend`` format. | |
| Parameters | |
| ---------- | |
| backend : str or None | |
| Name of backend, or None to use the default backend. | |
| Returns | |
| ------- | |
| backend : str | |
| The backend name. | |
| framework : str or None | |
| The GUI framework, which will be None for a backend that is non-interactive. | |
| """ | |
| if isinstance(backend, str): | |
| if not backend.startswith("module://"): | |
| backend = backend.lower() | |
| else: # Might be _auto_backend_sentinel or None | |
| # Use whatever is already running... | |
| from matplotlib import get_backend | |
| backend = get_backend() | |
| # Is backend already known (built-in or dynamically loaded)? | |
| gui = (self._BUILTIN_BACKEND_TO_GUI_FRAMEWORK.get(backend) or | |
| self._backend_to_gui_framework.get(backend)) | |
| # Is backend "module://something"? | |
| if gui is None and isinstance(backend, str) and backend.startswith("module://"): | |
| gui = "unknown" | |
| # Is backend a possible entry point? | |
| if gui is None and not self._loaded_entry_points: | |
| self._ensure_entry_points_loaded() | |
| gui = self._backend_to_gui_framework.get(backend) | |
| # Backend known but not its gui framework. | |
| if gui == "unknown": | |
| gui = self._get_gui_framework_by_loading(backend) | |
| self._backend_to_gui_framework[backend] = gui | |
| if gui is None: | |
| raise RuntimeError(f"'{backend}' is not a recognised backend name") | |
| return backend, gui if gui != "headless" else None | |
| def resolve_gui_or_backend(self, gui_or_backend): | |
| """ | |
| Return the backend and GUI framework for the specified string that may be | |
| either a GUI framework or a backend name, tested in that order. | |
| This is for use with the IPython %matplotlib magic command which may be a GUI | |
| framework such as ``%matplotlib qt`` or a backend name such as | |
| ``%matplotlib qtagg``. | |
| This function only loads entry points if they have not already been loaded and | |
| the backend is not built-in and not of ``module://some.backend`` format. | |
| Parameters | |
| ---------- | |
| gui_or_backend : str or None | |
| Name of GUI framework or backend, or None to use the default backend. | |
| Returns | |
| ------- | |
| backend : str | |
| The backend name. | |
| framework : str or None | |
| The GUI framework, which will be None for a backend that is non-interactive. | |
| """ | |
| if not gui_or_backend.startswith("module://"): | |
| gui_or_backend = gui_or_backend.lower() | |
| # First check if it is a gui loop name. | |
| backend = self.backend_for_gui_framework(gui_or_backend) | |
| if backend is not None: | |
| return backend, gui_or_backend if gui_or_backend != "headless" else None | |
| # Then check if it is a backend name. | |
| try: | |
| return self.resolve_backend(gui_or_backend) | |
| except Exception: # KeyError ? | |
| raise RuntimeError( | |
| f"'{gui_or_backend}' is not a recognised GUI loop or backend name") | |
| # Singleton | |
| backend_registry = BackendRegistry() | |