From 32935fd7614ecf528b5a40c88ff25c70c79c620a Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 19 Feb 2022 13:03:51 +0100 Subject: [PATCH 1/2] Deprecate backend_qt.qApp. This global has subtly different semantics from QtWidgets.QApplication.instance() (it is only updated when the private _create_qApp is called), and therefore seems not worth exposing. --- .../deprecations/22503-AL.rst | 3 + lib/matplotlib/backends/backend_qt.py | 122 +++++++++--------- 2 files changed, 66 insertions(+), 59 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/22503-AL.rst diff --git a/doc/api/next_api_changes/deprecations/22503-AL.rst b/doc/api/next_api_changes/deprecations/22503-AL.rst new file mode 100644 index 000000000000..fb5990e5c4f0 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/22503-AL.rst @@ -0,0 +1,3 @@ +``backend_qt.qApp`` +~~~~~~~~~~~~~~~~~~~ +... is deprecated. Use ``QtWidgets.QApplication.instance()`` instead. diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 76b4d74ef640..6e48362cba36 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -92,70 +92,73 @@ } -# make place holder -qApp = None +@_api.caching_module_getattr +class __getattr__: + qApp = _api.deprecated( + "3.6", alternative="QtWidgets.QApplication.instance()")( + property(lambda self: QtWidgets.QApplication.instance())) +# lru_cache keeps a reference to the QApplication instance, keeping it from +# being GC'd. +@functools.lru_cache(1) def _create_qApp(): - """ - Only one qApp can exist at a time, so check before creating one. - """ - global qApp - - if qApp is None: - app = QtWidgets.QApplication.instance() - if app is None: - # display_is_valid returns False only if on Linux and neither X11 - # nor Wayland display can be opened. - if not mpl._c_internal_utils.display_is_valid(): - raise RuntimeError('Invalid DISPLAY variable') - try: - QtWidgets.QApplication.setAttribute( - QtCore.Qt.AA_EnableHighDpiScaling) - except AttributeError: # Only for Qt>=5.6, <6. - pass - - # Check to make sure a QApplication from a different major version - # of Qt is not instantiated in the process - if QT_API in {'PyQt6', 'PySide6'}: - other_bindings = ('PyQt5', 'PySide2') - elif QT_API in {'PyQt5', 'PySide2'}: - other_bindings = ('PyQt6', 'PySide6') - else: - raise RuntimeError("Should never be here") - - for binding in other_bindings: - mod = sys.modules.get(f'{binding}.QtWidgets') - if mod is not None and mod.QApplication.instance() is not None: - other_core = sys.modules.get(f'{binding}.QtCore') - _api.warn_external( - f'Matplotlib is using {QT_API} which wraps ' - f'{QtCore.qVersion()} however an instantiated ' - f'QApplication from {binding} which wraps ' - f'{other_core.qVersion()} exists. Mixing Qt major ' - 'versions may not work as expected.' - ) - break - try: - QtWidgets.QApplication.setHighDpiScaleFactorRoundingPolicy( - QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) - except AttributeError: # Only for Qt>=5.14. - pass - qApp = QtWidgets.QApplication(["matplotlib"]) - if sys.platform == "darwin": - image = str(cbook._get_data_path('images/matplotlib.svg')) - icon = QtGui.QIcon(image) - qApp.setWindowIcon(icon) - qApp.lastWindowClosed.connect(qApp.quit) - cbook._setup_new_guiapp() + app = QtWidgets.QApplication.instance() + + # Create a new QApplication and configure if if non exists yet, as only one + # QApplication can exist at a time. + if app is None: + # display_is_valid returns False only if on Linux and neither X11 + # nor Wayland display can be opened. + if not mpl._c_internal_utils.display_is_valid(): + raise RuntimeError('Invalid DISPLAY variable') + + # Check to make sure a QApplication from a different major version + # of Qt is not instantiated in the process + if QT_API in {'PyQt6', 'PySide6'}: + other_bindings = ('PyQt5', 'PySide2') + elif QT_API in {'PyQt5', 'PySide2'}: + other_bindings = ('PyQt6', 'PySide6') else: - qApp = app + raise RuntimeError("Should never be here") + + for binding in other_bindings: + mod = sys.modules.get(f'{binding}.QtWidgets') + if mod is not None and mod.QApplication.instance() is not None: + other_core = sys.modules.get(f'{binding}.QtCore') + _api.warn_external( + f'Matplotlib is using {QT_API} which wraps ' + f'{QtCore.qVersion()} however an instantiated ' + f'QApplication from {binding} which wraps ' + f'{other_core.qVersion()} exists. Mixing Qt major ' + 'versions may not work as expected.' + ) + break + try: + QtWidgets.QApplication.setAttribute( + QtCore.Qt.AA_EnableHighDpiScaling) + except AttributeError: # Only for Qt>=5.6, <6. + pass + try: + QtWidgets.QApplication.setHighDpiScaleFactorRoundingPolicy( + QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) + except AttributeError: # Only for Qt>=5.14. + pass + app = QtWidgets.QApplication(["matplotlib"]) + if sys.platform == "darwin": + image = str(cbook._get_data_path('images/matplotlib.svg')) + icon = QtGui.QIcon(image) + app.setWindowIcon(icon) + app.lastWindowClosed.connect(app.quit) + cbook._setup_new_guiapp() try: - qApp.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps) # Only for Qt<6. + app.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps) # Only for Qt<6. except AttributeError: pass + return app + def _allow_super_init(__init__): """ @@ -419,7 +422,7 @@ def _get_key(self, event): def flush_events(self): # docstring inherited - qApp.processEvents() + QtWidgets.QApplication.instance().processEvents() def start_event_loop(self, timeout=0): # docstring inherited @@ -1022,7 +1025,7 @@ def trigger(self, *args): class ToolCopyToClipboardQT(backend_tools.ToolCopyToClipboardBase): def trigger(self, *args, **kwargs): pixmap = self.canvas.grab() - qApp.clipboard().setPixmap(pixmap) + QtWidgets.QApplication.instance().clipboard().setPixmap(pixmap) FigureManagerQT._toolbar2_class = NavigationToolbar2QT @@ -1036,5 +1039,6 @@ class _BackendQT(_Backend): @staticmethod def mainloop(): - with _maybe_allow_interrupt(qApp): - qt_compat._exec(qApp) + qapp = QtWidgets.QApplication.instance() + with _maybe_allow_interrupt(qapp): + qt_compat._exec(qapp) From 88377a3b840ddcf2cdd7f5d078b7bdc7f00a634a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 22 Mar 2022 01:02:04 -0400 Subject: [PATCH 2/2] TST: add test of qApp access warning --- .../tests/test_backends_interactive.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 23991410601c..81b9377c750e 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -267,7 +267,9 @@ def _implqt5agg(): assert 'PyQt5' in sys.modules or 'pyside2' in sys.modules import matplotlib.backends.backend_qt5 - matplotlib.backends.backend_qt5.qApp + with pytest.warns(DeprecationWarning, + match="QtWidgets.QApplication.instance"): + matplotlib.backends.backend_qt5.qApp def _implcairo(): @@ -279,7 +281,9 @@ def _implcairo(): assert 'PyQt5' in sys.modules or 'pyside2' in sys.modules import matplotlib.backends.backend_qt5 - matplotlib.backends.backend_qt5.qApp + with pytest.warns(DeprecationWarning, + match="QtWidgets.QApplication.instance"): + matplotlib.backends.backend_qt5.qApp def _implcore(): @@ -289,7 +293,10 @@ def _implcore(): assert 'PyQt6' not in sys.modules assert 'pyside6' not in sys.modules assert 'PyQt5' in sys.modules or 'pyside2' in sys.modules - matplotlib.backends.backend_qt5.qApp + + with pytest.warns(DeprecationWarning, + match="QtWidgets.QApplication.instance"): + matplotlib.backends.backend_qt5.qApp def test_qt5backends_uses_qt5(): @@ -410,11 +417,25 @@ def _lazy_headless(): @pytest.mark.skipif(sys.platform != "linux", reason="this a linux-only test") -@pytest.mark.backend('QtAgg', skip_on_importerror=True) +@pytest.mark.backend('Qt5Agg', skip_on_importerror=True) def test_lazy_linux_headless(): proc = _run_helper(_lazy_headless, timeout=_test_timeout, MPLBACKEND="") +def _qApp_warn_impl(): + import matplotlib.backends.backend_qt + import pytest + + with pytest.warns( + DeprecationWarning, match="QtWidgets.QApplication.instance"): + matplotlib.backends.backend_qt.qApp + + +@pytest.mark.backend('QtAgg', skip_on_importerror=True) +def test_qApp_warn(): + _run_helper(_qApp_warn_impl, timeout=_test_timeout) + + def _test_number_of_draws_script(): import matplotlib.pyplot as plt 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