spyder-ide / qtpy

Provides an uniform layer to support PyQt5, PySide2, PyQt6, PySide6 with a single codebase
MIT License
987 stars 153 forks source link

Qt namespace is littered with service functions #463

Open StSav012 opened 1 year ago

StSav012 commented 1 year ago

The following piece of code shows all the extra stuff QtPy adds to the PyQt/PySide modules:

# coding=utf-8
import importlib
import os
import re
import warnings
from types import ModuleType

warnings.filterwarnings("error")

def bright(text: str) -> str:
    return "\033[97m" + text + "\033[39m"

def italic(text: str) -> str:
    return "\033[3m" + text + "\033[23m"

api: str = 'PyQt5'

os.environ["FORCE_QT_API"] = "1"
os.environ["QT_API"] = api

try:
    import qtpy
except qtpy.PythonQtWarning as ex:
    print(bright(api), italic(str(ex)), sep=": ")

for module_name in [
    "Qsci",
    "Qt3DAnimation",
    "Qt3DCore",
    "Qt3DExtras",
    "Qt3DInput",
    "Qt3DLogic",
    "Qt3DRender",
    "QtAxContainer",
    "QtBluetooth",
    "QtCharts",
    "QtConcurrent",
    "QtCore",
    "QtDataVisualization",
    "QtDBus",
    "QtDesigner",
    "QtGui",
    "QtHelp",
    "QtLocation",
    "QtMacExtras",
    "QtMultimedia",
    "QtMultimediaWidgets",
    "QtNetwork",
    "QtNetworkAuth",
    "QtNfc",
    "QtOpenGL",
    "QtOpenGLWidgets",
    "QtPdf",
    "QtPdfWidgets",
    "QtPositioning",
    "QtPrintSupport",
    "QtPurchasing",
    "QtQml",
    "QtQuick",
    "QtQuick3D",
    "QtQuickControls2",
    "QtQuickWidgets",
    "QtRemoteObjects",
    "QtScxml",
    "QtSensors",
    "QtSerialPort",
    "QtSql",
    "QtStateMachine",
    "QtSvg",
    "QtSvgWidgets",
    "QtTest",
    "QtTextToSpeech",
    "QtUiTools",
    "QtWebChannel",
    "QtWebEngine",
    "QtWebEngineCore",
    "QtWebEngineQuick",
    "QtWebEngineWidgets",
    "QtWebSockets",
    "QtWidgets",
    "QtWinExtras",
    "QtX11Extras",
    "QtXml",
    "QtXmlPatterns",
]:
    try:
        original_module: ModuleType = importlib.import_module(
            f"{qtpy.API_NAME}.{module_name}"
        )
    except ModuleNotFoundError:
        # print(f"{module_name} not loaded from {API_NAME}")
        continue

    try:
        qtpy_module: ModuleType = importlib.import_module(f"qtpy.{module_name}")
    except (qtpy.QtBindingMissingModuleError, qtpy.QtModuleNotInQtVersionError) as ex:
        print(italic(str(ex)).replace(module_name, bright(module_name)))
        continue

    try:
        extra_members: frozenset[str] = (
            frozenset(dir(qtpy_module))
            - frozenset(dir(original_module))
            - frozenset(
                # To test if we are using WebEngine or WebKit
                # NOTE: This constant is imported by other projects
                # (e.g. Spyder), so please don't remove it.
                [
                    "WEBENGINE",
                ]
            )
            - frozenset(
                # These are for the compatibility b/w PySide and PyQt:
                [
                    "ClassInfo",
                    "ListProperty",
                    "MetaFunction",
                    "MetaSignal",
                    "Property",
                    "PyClassProperty",
                    "SIGNAL",
                    "SLOT",
                    "Signal",
                    "SignalInstance",
                    "Slot",
                    "VolatileBool",
                    "qjsEngine",
                    "qmlAttachedPropertiesObject",
                    "qmlClearTypeRegistrations",
                    "qmlRegisterRevision",
                    "qmlRegisterSingletonType",
                    "qmlRegisterUncreatableType",
                    "qmlTypeId",
                    "qtTrId",
                ]
            )
            - frozenset(
                # These are mostly harmless:
                [
                    "PYQT5",
                    "PYQT6",
                    "PYSIDE2",
                    "PYSIDE6",
                ]
            )
        )
    except Exception as ex:
        print(bright(f"{api}.{module_name}"), italic(str(ex)), sep=": ")
    else:
        neither_qt_nor_builtins: list[str] = sorted(
            filter(
                lambda d: re.match("(^(Q|q[A-Z]).+$|(^__[a-z_]+__$))", d) is None,
                extra_members,
            ),
        )
        if neither_qt_nor_builtins:
            print(
                bright(f"{api}.{module_name}"),
                ", ".join(neither_qt_nor_builtins),
                sep=": ",
            )

(It might be better to test the APIs one by one.)

For most of the modules, it's just PYQT5, PYQT6, PYSIDE2, and PYSIDE6. This can easily be cured with

del PYQT5, PYQT6, PYSIDE2, PYSIDE6

appended to the files. So, I've excluded them from the results for now. Still, I've got the following:

PyQt5.QtCore: TYPE_CHECKING, _qt_version, contextlib, parse, possibly_static_exec, possibly_static_exec_
PyQt5.QtDBus: sys
PyQt5.QtGui: _QAction, _QTOPENGL_NAMES, __QPointF, _action_set_shortcut, _action_set_shortcuts, _missing_optional_names, _qt_version, getattr_missing_optional_dep, parse, partialmethod, possibly_static_exec, set_shortcut, set_shortcuts
PyQt5.QtOpenGL: contextlib
PyQt5.QtWidgets: _QMenu, _QToolBar, _menu_add_action, _missing_optional_names, _qt_version, _toolbar_add_action, add_action, getattr_missing_optional_dep, parse, partialmethod, possibly_static_exec, static_method_kwargs_wrapper
PyQt5.QtX11Extras: sys

PySide2.QtCore: PySide2, TYPE_CHECKING, _qt_version, contextlib, parse, possibly_static_exec, possibly_static_exec_
PySide2.QtGui: _QAction, _QTOPENGL_NAMES, __QPointF, _action_set_shortcut, _action_set_shortcuts, _missing_optional_names, _qt_version, getattr_missing_optional_dep, movePosition, movePositionPatched, parse, partialmethod, possibly_static_exec, set_shortcut, set_shortcuts
PySide2.QtOpenGL: contextlib
PySide2.QtWidgets: _QMenu, _QToolBar, _menu_add_action, _missing_optional_names, _qt_version, _toolbar_add_action, add_action, getattr_missing_optional_dep, parse, partialmethod, possibly_static_exec, static_method_kwargs_wrapper
PySide2.QtX11Extras: sys

PyQt6.QtCore: TYPE_CHECKING, _qt_version, contextlib, parse, possibly_static_exec, possibly_static_exec_, promote_enums
PyQt6.QtDBus: sys
PyQt6.QtGui: _QTOPENGL_NAMES, _class, _missing_optional_names, _obsolete_function, _qt_version, getattr_missing_optional_dep, parse, partialmethod, possibly_static_exec, promote_enums, set_shortcut, set_shortcuts
PyQt6.QtOpenGL: contextlib
PyQt6.QtTest: promote_enums
PyQt6.QtWidgets: _missing_optional_names, _qt_version, add_action, getattr_missing_optional_dep, parse, partialmethod, possibly_static_exec, promote_enums, static_method_kwargs_wrapper

PySide6.QtCore: PySide6, TYPE_CHECKING, _qt_version, contextlib, parse, possibly_static_exec, possibly_static_exec_
PySide6.QtDBus: sys
PySide6.QtGui: _QTOPENGL_NAMES, _class, _missing_optional_names, _obsolete_function, _qt_version, getattr_missing_optional_dep, movePosition, movePositionPatched, parse, partialmethod, possibly_static_exec, set_shortcut, set_shortcuts
PySide6.QtOpenGL: contextlib
PySide6.QtWidgets: _missing_optional_names, _qt_version, add_action, getattr_missing_optional_dep, parse, partialmethod, possibly_static_exec, static_method_kwargs_wrapper

That's a lot, and most of this is what I've brought. 😢

ccordoba12 commented 1 year ago

I thought about a suggestion that could minimize the current changes you did in PR #465 to clear our names, and the future required ones: what if we import the _utils module instead of individual elements of it?

That way we'd only need to remove that module instead.

StSav012 commented 1 year ago

what if we import the _utils module instead of individual elements of it?

Done. Thank you for the suggestion! The code might have become just a bit slower, but also a bit clearer to read.