From 6cc6a98d39c1ec258cb45e4734e2e2af948c0d35 Mon Sep 17 00:00:00 2001 From: Mark Harfouche Date: Sat, 22 Jun 2024 10:03:15 -0400 Subject: [PATCH] Make QtWebEngine optional through lazy loading --- spyder/api/plugins/new_api.py | 7 ++++++ spyder/app/mainwindow.py | 23 +++++++++++++------ spyder/plugins/help/plugin.py | 1 + spyder/plugins/help/widgets.py | 11 +++++++-- .../ipythonconsole/widgets/main_widget.py | 12 +++++++--- spyder/plugins/onlinehelp/plugin.py | 1 + spyder/plugins/onlinehelp/widgets.py | 16 +++++++++++-- 7 files changed, 57 insertions(+), 14 deletions(-) diff --git a/spyder/api/plugins/new_api.py b/spyder/api/plugins/new_api.py index 186e70f0dcf..62d2a6f77ec 100644 --- a/spyder/api/plugins/new_api.py +++ b/spyder/api/plugins/new_api.py @@ -159,6 +159,13 @@ class SpyderPluginV2(QObject, SpyderActionMixin, SpyderConfigurationObserver, # disabled. Default: True CAN_BE_DISABLED = True + # Qt Web Widgets may be a heavy dependency for many packagers + # (e.g. conda-forge) + # We thus ask plugins to declare whether or not they need + # web widgets to enhance the distribution of Spyder to users + # https://github.com/spyder-ide/spyder/pull/22196#issuecomment-2189377043 + REQUIRE_WEB_WIDGETS = False + # --- API: Signals ------------------------------------------------------- # ------------------------------------------------------------------------ # Signals here are automatically connected by the Spyder main window and diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index 6e7146960d6..d2901ad8fab 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -53,7 +53,10 @@ from qtpy import QtSvg # analysis:ignore # Avoid a bug in Qt: https://bugreports.qt.io/browse/QTBUG-46720 -from qtpy import QtWebEngineWidgets # analysis:ignore +try: + from qtpy.QtWebEngineWidgets import WEBENGINE +except ImportError: + WEBENGINE = False from qtawesome.iconic_font import FontError @@ -724,12 +727,6 @@ def setup(self): for plugin in all_plugins.values(): plugin_name = plugin.NAME - # Disable plugins that use web widgets (currently Help and Online - # Help) if the user asks for it. - # See spyder-ide/spyder#16518 - if self._cli_options.no_web_widgets: - if "help" in plugin_name: - continue plugin_main_attribute_name = ( self._INTERNAL_PLUGINS_MAPPING[plugin_name] @@ -772,6 +769,18 @@ def setup(self): if plugin_name in enabled_plugins: PluginClass = internal_plugins[plugin_name] if issubclass(PluginClass, SpyderPluginV2): + # Disable plugins that use web widgets (currently Help and + # Online Help) if the user asks for it. + # See spyder-ide/spyder#16518 + # The plugins that require QtWebengine must declare + # themselves as needing that dependency + # https://github.com/spyder-ide/spyder/pull/22196#issuecomment-2189377043 + if PluginClass.REQUIRE_WEB_WIDGETS and ( + not WEBENGINE or + self._cli_options.no_web_widgets + ): + continue + PLUGIN_REGISTRY.register_plugin(self, PluginClass, external=False) diff --git a/spyder/plugins/help/plugin.py b/spyder/plugins/help/plugin.py index 41802862e89..f158f0a3d91 100644 --- a/spyder/plugins/help/plugin.py +++ b/spyder/plugins/help/plugin.py @@ -44,6 +44,7 @@ class Help(SpyderDockablePlugin): CONF_FILE = False LOG_PATH = get_conf_path(CONF_SECTION) DISABLE_ACTIONS_WHEN_HIDDEN = False + REQUIRE_WEB_WIDGETS = True # Signals sig_focus_changed = Signal() # TODO: What triggers this? diff --git a/spyder/plugins/help/widgets.py b/spyder/plugins/help/widgets.py index 7a465d1f2f8..7a164907029 100644 --- a/spyder/plugins/help/widgets.py +++ b/spyder/plugins/help/widgets.py @@ -17,7 +17,6 @@ from qtpy import PYQT5, PYQT6 from qtpy.QtCore import Qt, QUrl, Signal, Slot, QPoint from qtpy.QtGui import QColor -from qtpy.QtWebEngineWidgets import WEBENGINE, QWebEnginePage from qtpy.QtWidgets import (QActionGroup, QLabel, QLineEdit, QMessageBox, QSizePolicy, QStackedWidget, QVBoxLayout, QWidget) @@ -37,11 +36,16 @@ from spyder.utils.image_path_manager import get_image_path from spyder.utils.palette import SpyderPalette from spyder.utils.qthelpers import start_file -from spyder.widgets.browser import FrameWebView from spyder.widgets.comboboxes import EditableComboBox from spyder.widgets.findreplace import FindReplace from spyder.widgets.simplecodeeditor import SimpleCodeEditor +# In case WebEngine is not available (e.g. in Conda-forge) +try: + from qtpy.QtWebEngineWidgets import WEBENGINE +except ImportError: + WEBENGINE = False + # --- Constants # ---------------------------------------------------------------------------- @@ -158,6 +162,9 @@ def __init__(self, parent): QWidget.__init__(self, parent) SpyderWidgetMixin.__init__(self, class_parent=parent) + from qtpy.QtWebEngineWidgets import QWebEnginePage + from spyder.widgets.browser import FrameWebView + self.webview = FrameWebView(self) self.webview.setup() diff --git a/spyder/plugins/ipythonconsole/widgets/main_widget.py b/spyder/plugins/ipythonconsole/widgets/main_widget.py index 62cd047b1f9..091a119d77e 100644 --- a/spyder/plugins/ipythonconsole/widgets/main_widget.py +++ b/spyder/plugins/ipythonconsole/widgets/main_widget.py @@ -25,7 +25,6 @@ from qtpy.QtCore import Signal, Slot from qtpy.QtGui import QColor, QKeySequence from qtpy.QtPrintSupport import QPrintDialog, QPrinter -from qtpy.QtWebEngineWidgets import WEBENGINE from qtpy.QtWidgets import ( QApplication, QHBoxLayout, QLabel, QMessageBox, QVBoxLayout, QWidget) from traitlets.config.loader import Config, load_pyconfig_files @@ -58,11 +57,16 @@ from spyder.utils.palette import SpyderPalette from spyder.utils.stylesheet import AppStyle from spyder.utils.workers import WorkerManager -from spyder.widgets.browser import FrameWebView from spyder.widgets.findreplace import FindReplace from spyder.widgets.tabs import Tabs from spyder.widgets.printer import SpyderPrinter +# In case WebEngine is not available (e.g. in Conda-forge) +try: + from qtpy.QtWebEngineWidgets import WEBENGINE +except ImportError: + WEBENGINE = False + # Logging logger = logging.getLogger(__name__) @@ -252,7 +256,7 @@ def __init__(self, name=None, plugin=None, parent=None): self.enable_infowidget = True if plugin: cli_options = plugin.get_command_line_options() - if cli_options.no_web_widgets: + if cli_options.no_web_widgets or not WEBENGINE: self.enable_infowidget = False # Attrs for testing @@ -288,6 +292,8 @@ def __init__(self, name=None, plugin=None, parent=None): # Info widget if self.enable_infowidget: + from spyder.widgets.browser import FrameWebView + self.infowidget = FrameWebView(self) if WEBENGINE: self.infowidget.page().setBackgroundColor( diff --git a/spyder/plugins/onlinehelp/plugin.py b/spyder/plugins/onlinehelp/plugin.py index 976ec160cab..cd555143d67 100644 --- a/spyder/plugins/onlinehelp/plugin.py +++ b/spyder/plugins/onlinehelp/plugin.py @@ -32,6 +32,7 @@ class OnlineHelp(SpyderDockablePlugin): CONF_FILE = False WIDGET_CLASS = PydocBrowser LOG_PATH = get_conf_path(NAME) + REQUIRE_WEB_WIDGETS = True # --- Signals # ------------------------------------------------------------------------ diff --git a/spyder/plugins/onlinehelp/widgets.py b/spyder/plugins/onlinehelp/widgets.py index 211847595d4..6cf377bbafe 100644 --- a/spyder/plugins/onlinehelp/widgets.py +++ b/spyder/plugins/onlinehelp/widgets.py @@ -17,17 +17,21 @@ # Third party imports from qtpy.QtCore import Qt, QThread, QUrl, Signal, Slot from qtpy.QtGui import QCursor -from qtpy.QtWebEngineWidgets import WEBENGINE from qtpy.QtWidgets import QApplication, QLabel, QVBoxLayout # Local imports from spyder.api.translations import _ from spyder.api.widgets.main_widget import PluginMainWidget from spyder.plugins.onlinehelp.pydoc_patch import _start_server, _url_handler -from spyder.widgets.browser import FrameWebView, WebViewActions from spyder.widgets.comboboxes import UrlComboBox from spyder.widgets.findreplace import FindReplace +# In case WebEngine is not available (e.g. in Conda-forge) +try: + from qtpy.QtWebEngineWidgets import WEBENGINE +except ImportError: + WEBENGINE = False + # --- Constants # ---------------------------------------------------------------------------- @@ -139,6 +143,8 @@ class PydocBrowser(PluginMainWidget): """ def __init__(self, name=None, plugin=None, parent=None): + from spyder.widgets.browser import FrameWebView + super().__init__(name, plugin, parent=parent) self._is_running = False @@ -193,6 +199,8 @@ def get_focus_widget(self): return self.url_combo def setup(self): + from spyder.widgets.browser import WebViewActions + # Actions home_action = self.create_action( PydocBrowserActions.Home, @@ -243,6 +251,8 @@ def setup(self): self.sig_toggle_view_changed.connect(self.initialize) def update_actions(self): + from spyder.widgets.browser import WebViewActions + stop_action = self.get_action(WebViewActions.Stop) refresh_action = self.get_action(WebViewActions.Refresh) @@ -271,6 +281,8 @@ def _continue_initialization(self): def _handle_url_combo_activation(self): """Load URL from combo box first item.""" + from spyder.widgets.browser import WebViewActions + if not self._is_running: text = str(self.url_combo.currentText()) self.go_to(self.text_to_url(text))