diff --git a/qtpy/Qsci.py b/qtpy/Qsci.py index 85fec911..f4e2850a 100644 --- a/qtpy/Qsci.py +++ b/qtpy/Qsci.py @@ -34,3 +34,12 @@ ) from error elif PYSIDE2 or PYSIDE6: raise QtBindingMissingModuleError(name="Qsci") + +del ( + PYQT5, + PYQT6, + PYSIDE2, + PYSIDE6, + QtBindingMissingModuleError, + QtModuleNotInstalledError, +) diff --git a/qtpy/Qt3DAnimation.py b/qtpy/Qt3DAnimation.py index 8d68e593..ed5c4fe7 100644 --- a/qtpy/Qt3DAnimation.py +++ b/qtpy/Qt3DAnimation.py @@ -39,6 +39,7 @@ for __name in inspect.getmembers(__temp.Qt3DAnimation): globals()[__name[0]] = __name[1] + del __name, __temp, inspect elif PYSIDE6: # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-1026 import inspect @@ -47,3 +48,7 @@ for __name in inspect.getmembers(__temp.Qt3DAnimation): globals()[__name[0]] = __name[1] + del __name, __temp, inspect + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtModuleNotInstalledError diff --git a/qtpy/Qt3DCore.py b/qtpy/Qt3DCore.py index 5c15df19..46774a91 100644 --- a/qtpy/Qt3DCore.py +++ b/qtpy/Qt3DCore.py @@ -39,6 +39,7 @@ for __name in inspect.getmembers(__temp.Qt3DCore): globals()[__name[0]] = __name[1] + del __name, __temp, inspect elif PYSIDE6: # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-1026 import inspect @@ -47,3 +48,7 @@ for __name in inspect.getmembers(__temp.Qt3DCore): globals()[__name[0]] = __name[1] + del __name, __temp, inspect + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtModuleNotInstalledError diff --git a/qtpy/Qt3DExtras.py b/qtpy/Qt3DExtras.py index d146065d..c760cc18 100644 --- a/qtpy/Qt3DExtras.py +++ b/qtpy/Qt3DExtras.py @@ -39,6 +39,7 @@ for __name in inspect.getmembers(__temp.Qt3DExtras): globals()[__name[0]] = __name[1] + del __name, __temp, inspect elif PYSIDE6: # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-1026 import inspect @@ -47,3 +48,7 @@ for __name in inspect.getmembers(__temp.Qt3DExtras): globals()[__name[0]] = __name[1] + del __name, __temp, inspect + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtModuleNotInstalledError diff --git a/qtpy/Qt3DInput.py b/qtpy/Qt3DInput.py index 1dbc3b44..e115f30c 100644 --- a/qtpy/Qt3DInput.py +++ b/qtpy/Qt3DInput.py @@ -39,6 +39,7 @@ for __name in inspect.getmembers(__temp.Qt3DInput): globals()[__name[0]] = __name[1] + del __name, __temp, inspect elif PYSIDE6: # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-1026 import inspect @@ -47,3 +48,7 @@ for __name in inspect.getmembers(__temp.Qt3DInput): globals()[__name[0]] = __name[1] + del __name, __temp, inspect + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtModuleNotInstalledError diff --git a/qtpy/Qt3DLogic.py b/qtpy/Qt3DLogic.py index 3fea3f51..a6f9c33f 100644 --- a/qtpy/Qt3DLogic.py +++ b/qtpy/Qt3DLogic.py @@ -39,6 +39,7 @@ for __name in inspect.getmembers(__temp.Qt3DLogic): globals()[__name[0]] = __name[1] + del __name, __temp, inspect elif PYSIDE6: # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-1026 import inspect @@ -47,3 +48,7 @@ for __name in inspect.getmembers(__temp.Qt3DLogic): globals()[__name[0]] = __name[1] + del __name, __temp, inspect + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtModuleNotInstalledError diff --git a/qtpy/Qt3DRender.py b/qtpy/Qt3DRender.py index 72f44f2c..1e0b63b7 100644 --- a/qtpy/Qt3DRender.py +++ b/qtpy/Qt3DRender.py @@ -39,6 +39,7 @@ for __name in inspect.getmembers(__temp.Qt3DRender): globals()[__name[0]] = __name[1] + del __name, __temp, inspect elif PYSIDE6: # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-1026 import inspect @@ -47,3 +48,7 @@ for __name in inspect.getmembers(__temp.Qt3DRender): globals()[__name[0]] = __name[1] + del __name, __temp, inspect + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtModuleNotInstalledError diff --git a/qtpy/QtAxContainer.py b/qtpy/QtAxContainer.py index fa2ba4ed..c7eddda0 100644 --- a/qtpy/QtAxContainer.py +++ b/qtpy/QtAxContainer.py @@ -21,3 +21,6 @@ from PySide2.QtAxContainer import * elif PYSIDE6: from PySide6.QtAxContainer import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtBluetooth.py b/qtpy/QtBluetooth.py index 237f81f9..eb78aa27 100644 --- a/qtpy/QtBluetooth.py +++ b/qtpy/QtBluetooth.py @@ -23,3 +23,6 @@ raise QtBindingMissingModuleError(name="QtBluetooth") elif PYSIDE6: from PySide6.QtBluetooth import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtCharts.py b/qtpy/QtCharts.py index 81716b0f..29f78590 100644 --- a/qtpy/QtCharts.py +++ b/qtpy/QtCharts.py @@ -42,6 +42,10 @@ for __name in inspect.getmembers(__temp.QtCharts): globals()[__name[0]] = __name[1] + del __name, __temp, inspect elif PYSIDE6: from PySide6 import QtCharts from PySide6.QtCharts import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtModuleNotInstalledError diff --git a/qtpy/QtConcurrent.py b/qtpy/QtConcurrent.py index a8e3ed3f..3192d8df 100644 --- a/qtpy/QtConcurrent.py +++ b/qtpy/QtConcurrent.py @@ -21,3 +21,6 @@ from PySide2.QtConcurrent import * elif PYSIDE6: from PySide6.QtConcurrent import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtCore.py b/qtpy/QtCore.py index 09a42b1e..a1995067 100644 --- a/qtpy/QtCore.py +++ b/qtpy/QtCore.py @@ -8,13 +8,20 @@ """Provides QtCore classes and functions.""" import contextlib +from functools import partial, partialmethod from typing import TYPE_CHECKING from packaging.version import parse -from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6 -from . import QT_VERSION as _qt_version -from ._utils import possibly_static_exec, possibly_static_exec_ +from . import ( + PYQT5, + PYQT6, + PYSIDE2, + PYSIDE6, + QT_VERSION, + QtBindingsNotFoundError, + _utils, +) if PYQT5: from PyQt5.QtCore import * @@ -55,13 +62,16 @@ from PyQt6.QtGui import Qt # Map missing methods - QCoreApplication.exec_ = lambda *args, **kwargs: possibly_static_exec( - QCoreApplication, - *args, - **kwargs, + QCoreApplication.exec_ = partial( + lambda *args, _function, **kwargs: _function( + QCoreApplication, + *args, + **kwargs, + ), + _function=_utils.possibly_static_exec, ) - QEventLoop.exec_ = lambda self, *args, **kwargs: self.exec(*args, **kwargs) - QThread.exec_ = lambda self, *args, **kwargs: self.exec(*args, **kwargs) + QEventLoop.exec_ = partialmethod(QEventLoop.exec) + QThread.exec_ = partialmethod(QThread.exec) # Those are imported from `import *` del ( @@ -77,7 +87,7 @@ from .enums_compat import promote_enums promote_enums(QtCore) - del QtCore + del QtCore, promote_enums # Alias deprecated ItemDataRole enum values removed in Qt6 Qt.BackgroundColorRole = ( @@ -95,10 +105,8 @@ Qt.ItemFlags = lambda value=0: Qt.ItemFlag(value) elif PYSIDE2: - import PySide2.QtCore from PySide2.QtCore import * - - __version__ = PySide2.QtCore.__version__ + from PySide2.QtCore import __version__ # Missing QtGui utility functions on Qt if getattr(Qt, "mightBeRichText", None) is None: @@ -111,23 +119,21 @@ # Fails with PySide2 5.12.0 pass - QCoreApplication.exec = lambda *args, **kwargs: possibly_static_exec_( - QCoreApplication, - *args, - **kwargs, - ) - QEventLoop.exec = lambda self, *args, **kwargs: self.exec_(*args, **kwargs) - QThread.exec = lambda self, *args, **kwargs: self.exec_(*args, **kwargs) - QTextStreamManipulator.exec = lambda self, *args, **kwargs: self.exec_( - *args, - **kwargs, + QCoreApplication.exec = partial( + lambda *args, _function, **kwargs: _function( + QCoreApplication, + *args, + **kwargs, + ), + _function=_utils.possibly_static_exec_, ) + QEventLoop.exec = partialmethod(QEventLoop.exec_) + QThread.exec = partialmethod(QThread.exec_) + QTextStreamManipulator.exec = partialmethod(QTextStreamManipulator.exec_) elif PYSIDE6: - import PySide6.QtCore from PySide6.QtCore import * - - __version__ = PySide6.QtCore.__version__ + from PySide6.QtCore import __version__ # Missing QtGui utility functions on Qt if getattr(Qt, "mightBeRichText", None) is None: @@ -144,49 +150,31 @@ Qt.MidButton = Qt.MiddleButton # Map DeprecationWarning methods - QCoreApplication.exec_ = lambda *args, **kwargs: possibly_static_exec( - QCoreApplication, - *args, - **kwargs, - ) - QEventLoop.exec_ = lambda self, *args, **kwargs: self.exec(*args, **kwargs) - QThread.exec_ = lambda self, *args, **kwargs: self.exec(*args, **kwargs) - QTextStreamManipulator.exec_ = lambda self, *args, **kwargs: self.exec( - *args, - **kwargs, + QCoreApplication.exec_ = partial( + lambda *args, _function, **kwargs: _function( + QCoreApplication, + *args, + **kwargs, + ), + _function=_utils.possibly_static_exec, ) + QEventLoop.exec_ = partialmethod(QEventLoop.exec) + QThread.exec_ = partialmethod(QThread.exec) + QTextStreamManipulator.exec_ = partialmethod(QTextStreamManipulator.exec) # Passing as default value 0 in the same way PySide6 6.3.2 does for the `Qt.ItemFlags` definition. - if parse(_qt_version) > parse("6.3"): + if parse(QT_VERSION) > parse("6.3"): Qt.ItemFlags = lambda value=0: Qt.ItemFlag(value) # For issue #153 and updated for issue #305 if PYQT5 or PYQT6: - QDate.toPython = lambda self, *args, **kwargs: self.toPyDate( - *args, - **kwargs, - ) - QDateTime.toPython = lambda self, *args, **kwargs: self.toPyDateTime( - *args, - **kwargs, - ) - QTime.toPython = lambda self, *args, **kwargs: self.toPyTime( - *args, - **kwargs, - ) + QDate.toPython = partialmethod(QDate.toPyDate) + QDateTime.toPython = partialmethod(QDateTime.toPyDateTime) + QTime.toPython = partialmethod(QTime.toPyTime) if PYSIDE2 or PYSIDE6: - QDate.toPyDate = lambda self, *args, **kwargs: self.toPython( - *args, - **kwargs, - ) - QDateTime.toPyDateTime = lambda self, *args, **kwargs: self.toPython( - *args, - **kwargs, - ) - QTime.toPyTime = lambda self, *args, **kwargs: self.toPython( - *args, - **kwargs, - ) + QDate.toPyDate = partialmethod(QDate.toPython) + QDateTime.toPyDateTime = partialmethod(QDateTime.toPython) + QTime.toPyTime = partialmethod(QTime.toPython) # Mirror https://github.com/spyder-ide/qtpy/pull/393 if PYQT5 or PYSIDE2: @@ -195,3 +183,24 @@ if PYQT6 or PYSIDE6: QLibraryInfo.location = QLibraryInfo.path QLibraryInfo.LibraryLocation = QLibraryInfo.LibraryPath + +# If something is imported, `__version__` ought to be defined. +try: + assert __version__ +except (NameError, AssertionError): + raise QtBindingsNotFoundError from None + +# Clean up the namespace +del ( + PYQT5, + PYQT6, + PYSIDE2, + PYSIDE6, + QT_VERSION, + QtBindingsNotFoundError, + _utils, +) +del TYPE_CHECKING +del contextlib +del partial, partialmethod +del parse diff --git a/qtpy/QtDBus.py b/qtpy/QtDBus.py index 1d41aab9..44565618 100644 --- a/qtpy/QtDBus.py +++ b/qtpy/QtDBus.py @@ -29,3 +29,14 @@ from PySide6.QtDBus import * else: raise QtModuleNotInOSError(name="QtDBus") + +# Clean up the namespace +del ( + PYQT5, + PYQT6, + PYSIDE2, + PYSIDE6, + QtBindingMissingModuleError, + QtModuleNotInOSError, +) +del sys diff --git a/qtpy/QtDataVisualization.py b/qtpy/QtDataVisualization.py index 0a4facf8..636f2e99 100644 --- a/qtpy/QtDataVisualization.py +++ b/qtpy/QtDataVisualization.py @@ -39,5 +39,9 @@ for __name in inspect.getmembers(__temp.QtDataVisualization): globals()[__name[0]] = __name[1] + del __name, __temp, inspect elif PYSIDE6: from PySide6.QtDataVisualization import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtModuleNotInstalledError diff --git a/qtpy/QtDesigner.py b/qtpy/QtDesigner.py index acf61d57..39e03d50 100644 --- a/qtpy/QtDesigner.py +++ b/qtpy/QtDesigner.py @@ -23,3 +23,6 @@ raise QtBindingMissingModuleError(name="QtDesigner") elif PYSIDE6: from PySide6.QtDesigner import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtGui.py b/qtpy/QtGui.py index 8b3a0ba9..066e1877 100644 --- a/qtpy/QtGui.py +++ b/qtpy/QtGui.py @@ -7,11 +7,9 @@ # ----------------------------------------------------------------------------- """Provides QtGui classes and functions.""" +from functools import partial, partialmethod -from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtModuleNotInstalledError -from ._utils import getattr_missing_optional_dep, possibly_static_exec - -_missing_optional_names = {} +from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6, _utils _QTOPENGL_NAMES = { "QOpenGLBuffer", @@ -32,15 +30,20 @@ } -def __getattr__(name): +def __getattr__(attr): """Custom getattr to chain and wrap errors due to missing optional deps.""" + from ._utils import getattr_missing_optional_dep + raise getattr_missing_optional_dep( - name, + attr, module_name=__name__, - optional_names=_missing_optional_names, + optional_names=getattr(__getattr__, "missing_optional_names", {}), ) +__getattr__.missing_optional_names = {} + + if PYQT5: from PyQt5.QtGui import * @@ -64,7 +67,7 @@ def __getattr__(name): from PyQt6.QtOpenGL import * except ImportError as error: for name in _QTOPENGL_NAMES: - _missing_optional_names[name] = { + __getattr__.missing_optional_names[name] = { "name": "PyQt6.QtOpenGL", "missing_package": "pyopengl", "import_error": error, @@ -80,22 +83,22 @@ def __getattr__(name): ) # Map missing/renamed methods - QDrag.exec_ = lambda self, *args, **kwargs: self.exec(*args, **kwargs) - QGuiApplication.exec_ = lambda *args, **kwargs: possibly_static_exec( - QGuiApplication, - *args, - **kwargs, - ) - QTextDocument.print_ = lambda self, *args, **kwargs: self.print( - *args, - **kwargs, + QDrag.exec_ = partialmethod(QDrag.exec) + QGuiApplication.exec_ = partial( + lambda *args, _function, **kwargs: _function( + QGuiApplication, + *args, + **kwargs, + ), + _function=_utils.possibly_static_exec, ) + QTextDocument.print_ = partialmethod(QTextDocument.print) # Allow unscoped access for enums inside the QtGui module from .enums_compat import promote_enums promote_enums(QtGui) - del QtGui + del QtGui, promote_enums elif PYSIDE2: from PySide2.QtGui import * @@ -110,12 +113,7 @@ def __getattr__(name): if hasattr(QFontMetrics, "horizontalAdvance"): # Needed to prevent raising a DeprecationWarning when using QFontMetrics.width - QFontMetrics.width = ( - lambda self, *args, **kwargs: self.horizontalAdvance( - *args, - **kwargs, - ) - ) + QFontMetrics.width = partialmethod(QFontMetrics.horizontalAdvance) elif PYSIDE6: from PySide6.QtGui import * @@ -126,7 +124,7 @@ def __getattr__(name): from PySide6.QtOpenGL import * except ImportError as error: for name in _QTOPENGL_NAMES: - _missing_optional_names[name] = { + __getattr__.missing_optional_names[name] = { "name": "PySide6.QtOpenGL", "missing_package": "pyopengl", "import_error": error, @@ -135,21 +133,18 @@ def __getattr__(name): # Backport `QFileSystemModel` moved to QtGui in Qt6 from PySide6.QtWidgets import QFileSystemModel - QFontMetrics.width = lambda self, *args, **kwargs: self.horizontalAdvance( - *args, - **kwargs, - ) - QFontMetricsF.width = lambda self, *args, **kwargs: self.horizontalAdvance( - *args, - **kwargs, - ) + QFontMetrics.width = partialmethod(QFontMetrics.horizontalAdvance) + QFontMetricsF.width = partialmethod(QFontMetricsF.horizontalAdvance) # Map DeprecationWarning methods - QDrag.exec_ = lambda self, *args, **kwargs: self.exec(*args, **kwargs) - QGuiApplication.exec_ = lambda *args, **kwargs: possibly_static_exec( - QGuiApplication, - *args, - **kwargs, + QDrag.exec_ = partialmethod(QDrag.exec) + QGuiApplication.exec_ = partial( + lambda *args, _function, **kwargs: _function( + QGuiApplication, + *args, + **kwargs, + ), + _function=_utils.possibly_static_exec, ) if PYSIDE2 or PYSIDE6: @@ -166,41 +161,45 @@ def __getattr__(name): # 6.3.0; older version, down to PySide 1, are probably affected as well [1]. # # * PySide2 5.15.0 and 5.15.2.1 silently ignore invalid keyword arguments, - # i.e. passing the `mode` keyword argument has no effect and doesn`t + # i.e. passing the `mode` keyword argument has no effect and doesn't # raise an exception. Older versions, down to PySide 1, are probably # affected as well [1]. At least PySide2 5.15.3 and PySide6 6.3.0 raise an # exception when `mode` or any other invalid keyword argument is passed. # # [1] https://bugreports.qt.io/browse/PYSIDE-185 - movePosition = QTextCursor.movePosition def movePositionPatched( self, operation: QTextCursor.MoveOperation, mode: QTextCursor.MoveMode = QTextCursor.MoveAnchor, n: int = 1, + *, + movePosition, ) -> bool: return movePosition(self, operation, mode, n) - QTextCursor.movePosition = movePositionPatched + QTextCursor.movePosition = partialmethod( + movePositionPatched, + movePosition=QTextCursor.movePosition, + ) + + del movePositionPatched if PYQT5 or PYSIDE2: # Part of the fix for https://github.com/spyder-ide/qtpy/issues/394 - from qtpy.QtCore import QPointF as __QPointF - QNativeGestureEvent.x = lambda self: self.localPos().toPoint().x() QNativeGestureEvent.y = lambda self: self.localPos().toPoint().y() QNativeGestureEvent.position = lambda self: self.localPos() QNativeGestureEvent.globalX = lambda self: self.globalPos().x() QNativeGestureEvent.globalY = lambda self: self.globalPos().y() - QNativeGestureEvent.globalPosition = lambda self: __QPointF( - float(self.globalPos().x()), - float(self.globalPos().y()), + QNativeGestureEvent.globalPosition = partialmethod( + _utils.to_q_point_f, + get_point_method="globalPos", ) QEnterEvent.position = lambda self: self.localPos() - QEnterEvent.globalPosition = lambda self: __QPointF( - float(self.globalX()), - float(self.globalY()), + QEnterEvent.globalPosition = partialmethod( + _utils.to_q_point_f, + get_point_method="globalPos", ) QTabletEvent.position = lambda self: self.posF() QTabletEvent.globalPosition = lambda self: self.globalPosF() @@ -210,9 +209,9 @@ def movePositionPatched( # No `QHoverEvent.globalPosition`, `QHoverEvent.globalX`, # nor `QHoverEvent.globalY` in the Qt5 docs. QMouseEvent.position = lambda self: self.localPos() - QMouseEvent.globalPosition = lambda self: __QPointF( - float(self.globalX()), - float(self.globalY()), + QMouseEvent.globalPosition = partialmethod( + _utils.to_q_point_f, + get_point_method="globalPos", ) # Follow similar approach for `QDropEvent` and child classes @@ -236,6 +235,8 @@ def movePositionPatched( ): if hasattr(_class, _obsolete_function): delattr(_class, _obsolete_function) + del _class, _obsolete_function + QSinglePointEvent.pos = lambda self: self.position().toPoint() QSinglePointEvent.posF = lambda self: self.position() QSinglePointEvent.localPos = lambda self: self.position() @@ -252,3 +253,8 @@ def movePositionPatched( # Follow similar approach for `QDropEvent` and child classes QDropEvent.pos = lambda self: self.position().toPoint() QDropEvent.posF = lambda self: self.position() + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, _utils +del partial, partialmethod +del _QTOPENGL_NAMES diff --git a/qtpy/QtHelp.py b/qtpy/QtHelp.py index d0e2babb..d5cdcdfe 100644 --- a/qtpy/QtHelp.py +++ b/qtpy/QtHelp.py @@ -17,3 +17,6 @@ from PySide2.QtHelp import * elif PYSIDE6: from PySide6.QtHelp import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtLocation.py b/qtpy/QtLocation.py index 160c9476..7a4a87d6 100644 --- a/qtpy/QtLocation.py +++ b/qtpy/QtLocation.py @@ -23,3 +23,6 @@ from PySide2.QtLocation import * elif PYSIDE6: raise QtBindingMissingModuleError(name="QtLocation") + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtMacExtras.py b/qtpy/QtMacExtras.py index 58d23b8c..d14d5c79 100644 --- a/qtpy/QtMacExtras.py +++ b/qtpy/QtMacExtras.py @@ -29,3 +29,14 @@ raise QtModuleNotInQtVersionError(name="QtMacExtras") else: raise QtModuleNotInOSError(name="QtMacExtras") + +# Clean up the namespace +del ( + PYQT5, + PYQT6, + PYSIDE2, + PYSIDE6, + QtModuleNotInOSError, + QtModuleNotInQtVersionError, +) +del sys diff --git a/qtpy/QtMultimedia.py b/qtpy/QtMultimedia.py index 7403e64d..7bb166ff 100644 --- a/qtpy/QtMultimedia.py +++ b/qtpy/QtMultimedia.py @@ -17,3 +17,6 @@ from PySide2.QtMultimedia import * elif PYSIDE6: from PySide6.QtMultimedia import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtMultimediaWidgets.py b/qtpy/QtMultimediaWidgets.py index 69af111a..eb390495 100644 --- a/qtpy/QtMultimediaWidgets.py +++ b/qtpy/QtMultimediaWidgets.py @@ -17,3 +17,6 @@ from PySide2.QtMultimediaWidgets import * elif PYSIDE6: from PySide6.QtMultimediaWidgets import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtNetwork.py b/qtpy/QtNetwork.py index 2c4e5476..c0868c04 100644 --- a/qtpy/QtNetwork.py +++ b/qtpy/QtNetwork.py @@ -18,3 +18,6 @@ from PySide2.QtNetwork import * elif PYSIDE6: from PySide6.QtNetwork import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtNetworkAuth.py b/qtpy/QtNetworkAuth.py index f49adcec..4c33a0ec 100644 --- a/qtpy/QtNetworkAuth.py +++ b/qtpy/QtNetworkAuth.py @@ -36,3 +36,13 @@ raise QtBindingMissingModuleError(name="QtNetworkAuth") elif PYSIDE6: from PySide6.QtNetworkAuth import * + +# Clean up the namespace +del ( + PYQT5, + PYQT6, + PYSIDE2, + PYSIDE6, + QtBindingMissingModuleError, + QtModuleNotInstalledError, +) diff --git a/qtpy/QtNfc.py b/qtpy/QtNfc.py index 8dafd7ce..7249c9dd 100644 --- a/qtpy/QtNfc.py +++ b/qtpy/QtNfc.py @@ -23,3 +23,6 @@ raise QtBindingMissingModuleError(name="QtNfc") elif PYSIDE6: from PySide6.QtNfc import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtOpenGL.py b/qtpy/QtOpenGL.py index af881ed9..168e5e3e 100644 --- a/qtpy/QtOpenGL.py +++ b/qtpy/QtOpenGL.py @@ -64,3 +64,7 @@ # These are not present on some architectures such as armhf with contextlib.suppress(ImportError): from PySide2.QtGui import QOpenGLTimeMonitor, QOpenGLTimerQuery + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 +del contextlib diff --git a/qtpy/QtOpenGLWidgets.py b/qtpy/QtOpenGLWidgets.py index c000e0fd..efc28948 100644 --- a/qtpy/QtOpenGLWidgets.py +++ b/qtpy/QtOpenGLWidgets.py @@ -23,3 +23,6 @@ raise QtBindingMissingModuleError(name="QtOpenGLWidgets") elif PYSIDE6: from PySide6.QtOpenGLWidgets import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtPdf.py b/qtpy/QtPdf.py index f98cbd0f..239f9d8a 100644 --- a/qtpy/QtPdf.py +++ b/qtpy/QtPdf.py @@ -25,3 +25,6 @@ elif PYSIDE6: # Available with version >=6.4.0 from PySide6.QtPdf import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtPdfWidgets.py b/qtpy/QtPdfWidgets.py index a437db60..bdd3fd54 100644 --- a/qtpy/QtPdfWidgets.py +++ b/qtpy/QtPdfWidgets.py @@ -25,3 +25,6 @@ elif PYSIDE6: # Available with version >=6.4.0 from PySide6.QtPdfWidgets import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtPositioning.py b/qtpy/QtPositioning.py index 5083cee7..8c94c55f 100644 --- a/qtpy/QtPositioning.py +++ b/qtpy/QtPositioning.py @@ -17,3 +17,6 @@ from PySide2.QtPositioning import * elif PYSIDE6: from PySide6.QtPositioning import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtPrintSupport.py b/qtpy/QtPrintSupport.py index d78ff4cc..6fbb1553 100644 --- a/qtpy/QtPrintSupport.py +++ b/qtpy/QtPrintSupport.py @@ -40,3 +40,6 @@ ) elif PYSIDE2: from PySide2.QtPrintSupport import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtPurchasing.py b/qtpy/QtPurchasing.py index fd694483..e7540003 100644 --- a/qtpy/QtPurchasing.py +++ b/qtpy/QtPurchasing.py @@ -26,3 +26,13 @@ ) from error elif PYQT6 or PYSIDE2 or PYSIDE6: raise QtBindingMissingModuleError(name="QtPurchasing") + +# Clean up the namespace +del ( + PYQT5, + PYQT6, + PYSIDE2, + PYSIDE6, + QtBindingMissingModuleError, + QtModuleNotInstalledError, +) diff --git a/qtpy/QtQml.py b/qtpy/QtQml.py index 9d07f0e8..39d63eb9 100644 --- a/qtpy/QtQml.py +++ b/qtpy/QtQml.py @@ -17,3 +17,6 @@ from PySide6.QtQml import * elif PYSIDE2: from PySide2.QtQml import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtQuick.py b/qtpy/QtQuick.py index 1c2b7da3..49cbfb41 100644 --- a/qtpy/QtQuick.py +++ b/qtpy/QtQuick.py @@ -17,3 +17,6 @@ from PySide6.QtQuick import * elif PYSIDE2: from PySide2.QtQuick import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtQuick3D.py b/qtpy/QtQuick3D.py index a8138f97..b9f65f8e 100644 --- a/qtpy/QtQuick3D.py +++ b/qtpy/QtQuick3D.py @@ -23,3 +23,6 @@ raise QtBindingMissingModuleError(name="QtQuick3D") elif PYSIDE6: from PySide6.QtQuick3D import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtQuickControls2.py b/qtpy/QtQuickControls2.py index 634d5445..45719268 100644 --- a/qtpy/QtQuickControls2.py +++ b/qtpy/QtQuickControls2.py @@ -21,3 +21,6 @@ from PySide2.QtQuickControls2 import * elif PYSIDE6: from PySide6.QtQuickControls2 import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtQuickWidgets.py b/qtpy/QtQuickWidgets.py index 136b411e..8a756320 100644 --- a/qtpy/QtQuickWidgets.py +++ b/qtpy/QtQuickWidgets.py @@ -17,3 +17,6 @@ from PySide6.QtQuickWidgets import * elif PYSIDE2: from PySide2.QtQuickWidgets import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtRemoteObjects.py b/qtpy/QtRemoteObjects.py index 1035586b..00675453 100644 --- a/qtpy/QtRemoteObjects.py +++ b/qtpy/QtRemoteObjects.py @@ -17,3 +17,6 @@ from PySide6.QtRemoteObjects import * elif PYSIDE2: from PySide2.QtRemoteObjects import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtScxml.py b/qtpy/QtScxml.py index 40da5ef7..799f2a33 100644 --- a/qtpy/QtScxml.py +++ b/qtpy/QtScxml.py @@ -21,3 +21,6 @@ from PySide2.QtScxml import * elif PYSIDE6: from PySide6.QtScxml import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtSensors.py b/qtpy/QtSensors.py index 25b29190..6ef52e61 100644 --- a/qtpy/QtSensors.py +++ b/qtpy/QtSensors.py @@ -17,3 +17,6 @@ from PySide6.QtSensors import * elif PYSIDE2: from PySide2.QtSensors import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtSerialPort.py b/qtpy/QtSerialPort.py index 878c35b1..3f79d7cb 100644 --- a/qtpy/QtSerialPort.py +++ b/qtpy/QtSerialPort.py @@ -18,3 +18,6 @@ from PySide6.QtSerialPort import * elif PYSIDE2: from PySide2.QtSerialPort import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtSql.py b/qtpy/QtSql.py index 76a63760..f7f7bf8f 100644 --- a/qtpy/QtSql.py +++ b/qtpy/QtSql.py @@ -32,3 +32,5 @@ QSqlResult.exec_ = lambda self, *args, **kwargs: self.exec(*args, **kwargs) elif PYSIDE2: from PySide2.QtSql import * + +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtStateMachine.py b/qtpy/QtStateMachine.py index 343ce4a3..7df65465 100644 --- a/qtpy/QtStateMachine.py +++ b/qtpy/QtStateMachine.py @@ -19,3 +19,6 @@ raise QtBindingMissingModuleError(name="QtStateMachine") elif PYSIDE6: from PySide6.QtStateMachine import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtSvg.py b/qtpy/QtSvg.py index 0ee4f9e1..c69e1b9a 100644 --- a/qtpy/QtSvg.py +++ b/qtpy/QtSvg.py @@ -17,3 +17,6 @@ from PySide2.QtSvg import * elif PYSIDE6: from PySide6.QtSvg import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtSvgWidgets.py b/qtpy/QtSvgWidgets.py index 7e91dfc9..afcf63cf 100644 --- a/qtpy/QtSvgWidgets.py +++ b/qtpy/QtSvgWidgets.py @@ -23,3 +23,6 @@ raise QtBindingMissingModuleError(name="QtSvgWidgets") elif PYSIDE6: from PySide6.QtSvgWidgets import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtTest.py b/qtpy/QtTest.py index b14418f2..e747479d 100644 --- a/qtpy/QtTest.py +++ b/qtpy/QtTest.py @@ -20,8 +20,11 @@ from .enums_compat import promote_enums promote_enums(QtTest) - del QtTest + del QtTest, promote_enums elif PYSIDE2: from PySide2.QtTest import * elif PYSIDE6: from PySide6.QtTest import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtTextToSpeech.py b/qtpy/QtTextToSpeech.py index c37192a1..3d3618be 100644 --- a/qtpy/QtTextToSpeech.py +++ b/qtpy/QtTextToSpeech.py @@ -23,3 +23,6 @@ from PySide2.QtTextToSpeech import * elif PYSIDE6: raise QtBindingMissingModuleError(name="QtTextToSpeech") + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtUiTools.py b/qtpy/QtUiTools.py index ceca1efa..4d8c0136 100644 --- a/qtpy/QtUiTools.py +++ b/qtpy/QtUiTools.py @@ -21,3 +21,6 @@ from PySide2.QtUiTools import * elif PYSIDE6: from PySide6.QtUiTools import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/QtWebChannel.py b/qtpy/QtWebChannel.py index b2c35fff..eb6f5005 100644 --- a/qtpy/QtWebChannel.py +++ b/qtpy/QtWebChannel.py @@ -17,3 +17,6 @@ from PySide2.QtWebChannel import * elif PYSIDE6: from PySide6.QtWebChannel import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtWebEngine.py b/qtpy/QtWebEngine.py index 7cc08ff0..a8676c98 100644 --- a/qtpy/QtWebEngine.py +++ b/qtpy/QtWebEngine.py @@ -31,3 +31,6 @@ from PySide2.QtWebEngine import * elif PYSIDE6: raise QtModuleNotInQtVersionError(name="QtWebEngine") + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtWebEngineCore.py b/qtpy/QtWebEngineCore.py index 69aa4eea..7d1f177e 100644 --- a/qtpy/QtWebEngineCore.py +++ b/qtpy/QtWebEngineCore.py @@ -35,3 +35,6 @@ from PySide2.QtWebEngineCore import * elif PYSIDE6: from PySide6.QtWebEngineCore import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtModuleNotInstalledError diff --git a/qtpy/QtWebEngineQuick.py b/qtpy/QtWebEngineQuick.py index 717ac94d..861f8520 100644 --- a/qtpy/QtWebEngineQuick.py +++ b/qtpy/QtWebEngineQuick.py @@ -30,3 +30,13 @@ raise QtBindingMissingModuleError(name="QtWebEngineQuick") elif PYSIDE6: from PySide6.QtWebEngineQuick import * + +# Clean up the namespace +del ( + PYQT5, + PYQT6, + PYSIDE2, + PYSIDE6, + QtBindingMissingModuleError, + QtModuleNotInstalledError, +) diff --git a/qtpy/QtWebEngineWidgets.py b/qtpy/QtWebEngineWidgets.py index a39885f3..19615ed3 100644 --- a/qtpy/QtWebEngineWidgets.py +++ b/qtpy/QtWebEngineWidgets.py @@ -68,3 +68,6 @@ QWebEngineSettings, ) from PySide6.QtWebEngineWidgets import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtModuleNotInstalledError diff --git a/qtpy/QtWebSockets.py b/qtpy/QtWebSockets.py index a9bd33d9..eec3b408 100644 --- a/qtpy/QtWebSockets.py +++ b/qtpy/QtWebSockets.py @@ -17,3 +17,6 @@ from PySide2.QtWebSockets import * elif PYSIDE6: from PySide6.QtWebSockets import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtWidgets.py b/qtpy/QtWidgets.py index 5e8e0886..064ded58 100644 --- a/qtpy/QtWidgets.py +++ b/qtpy/QtWidgets.py @@ -7,31 +7,27 @@ # ----------------------------------------------------------------------------- """Provides widget classes and functions.""" -from functools import partialmethod +from functools import partial, partialmethod from packaging.version import parse -from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6 -from . import QT_VERSION as _qt_version -from ._utils import ( - add_action, - getattr_missing_optional_dep, - possibly_static_exec, - static_method_kwargs_wrapper, -) +from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6, QT_VERSION, _utils -_missing_optional_names = {} - -def __getattr__(name): +def __getattr__(attr): """Custom getattr to chain and wrap errors due to missing optional deps.""" + from ._utils import getattr_missing_optional_dep + raise getattr_missing_optional_dep( - name, + attr, module_name=__name__, - optional_names=_missing_optional_names, + optional_names=__getattr__.missing_optional_names, ) +__getattr__.missing_optional_names = {} + + if PYQT5: from PyQt5.QtWidgets import * elif PYQT6: @@ -51,43 +47,33 @@ def __getattr__(name): try: from PyQt6.QtOpenGLWidgets import QOpenGLWidget except ImportError as error: - _missing_optional_names["QOpenGLWidget"] = { + __getattr__.missing_optional_names["QOpenGLWidget"] = { "name": "PyQt6.QtOpenGLWidgets", "missing_package": "pyopengl", "import_error": error, } # Map missing/renamed methods - QTextEdit.setTabStopWidth = ( - lambda self, *args, **kwargs: self.setTabStopDistance(*args, **kwargs) - ) - QTextEdit.tabStopWidth = ( - lambda self, *args, **kwargs: self.tabStopDistance(*args, **kwargs) - ) - QTextEdit.print_ = lambda self, *args, **kwargs: self.print( - *args, - **kwargs, - ) - QPlainTextEdit.setTabStopWidth = ( - lambda self, *args, **kwargs: self.setTabStopDistance(*args, **kwargs) - ) - QPlainTextEdit.tabStopWidth = ( - lambda self, *args, **kwargs: self.tabStopDistance(*args, **kwargs) - ) - QPlainTextEdit.print_ = lambda self, *args, **kwargs: self.print( - *args, - **kwargs, - ) - QApplication.exec_ = lambda *args, **kwargs: possibly_static_exec( - QApplication, - *args, - **kwargs, - ) - QDialog.exec_ = lambda self, *args, **kwargs: self.exec(*args, **kwargs) - QMenu.exec_ = lambda *args, **kwargs: possibly_static_exec( - QMenu, - *args, - **kwargs, + QTextEdit.setTabStopWidth = partialmethod(QTextEdit.setTabStopDistance) + QTextEdit.tabStopWidth = partialmethod(QTextEdit.tabStopDistance) + QTextEdit.print_ = partialmethod(QTextEdit.print) + QPlainTextEdit.setTabStopWidth = partialmethod( + QPlainTextEdit.setTabStopDistance, + ) + QPlainTextEdit.tabStopWidth = partialmethod(QPlainTextEdit.tabStopDistance) + QPlainTextEdit.print_ = partialmethod(QPlainTextEdit.print) + QApplication.exec_ = partial( + lambda *args, _function, **kwargs: _function( + QApplication, + *args, + **kwargs, + ), + _function=_utils.possibly_static_exec, + ) + QDialog.exec_ = partialmethod(QDialog.exec) + QMenu.exec_ = partialmethod( + lambda *args, _function, **kwargs: _function(QMenu, *args, **kwargs), + _function=_utils.possibly_static_exec, ) QLineEdit.getTextMargins = lambda self: ( self.textMargins().left(), @@ -106,7 +92,7 @@ def __getattr__(name): from .enums_compat import promote_enums promote_enums(QtWidgets) - del QtWidgets + del QtWidgets, promote_enums elif PYSIDE2: from PySide2.QtWidgets import * elif PYSIDE6: @@ -119,25 +105,19 @@ def __getattr__(name): try: from PySide6.QtOpenGLWidgets import QOpenGLWidget except ImportError as error: - _missing_optional_names["QOpenGLWidget"] = { + __getattr__.missing_optional_names["QOpenGLWidget"] = { "name": "PySide6.QtOpenGLWidgets", "missing_package": "pyopengl", "import_error": error, } # Map missing/renamed methods - QTextEdit.setTabStopWidth = ( - lambda self, *args, **kwargs: self.setTabStopDistance(*args, **kwargs) - ) - QTextEdit.tabStopWidth = ( - lambda self, *args, **kwargs: self.tabStopDistance(*args, **kwargs) - ) - QPlainTextEdit.setTabStopWidth = ( - lambda self, *args, **kwargs: self.setTabStopDistance(*args, **kwargs) - ) - QPlainTextEdit.tabStopWidth = ( - lambda self, *args, **kwargs: self.tabStopDistance(*args, **kwargs) + QTextEdit.setTabStopWidth = partialmethod(QTextEdit.setTabStopDistance) + QTextEdit.tabStopWidth = partialmethod(QTextEdit.tabStopDistance) + QPlainTextEdit.setTabStopWidth = partialmethod( + QPlainTextEdit.setTabStopDistance, ) + QPlainTextEdit.tabStopWidth = partialmethod(QPlainTextEdit.tabStopDistance) QLineEdit.getTextMargins = lambda self: ( self.textMargins().left(), self.textMargins().top(), @@ -146,72 +126,82 @@ def __getattr__(name): ) # Map DeprecationWarning methods - QApplication.exec_ = lambda *args, **kwargs: possibly_static_exec( - QApplication, - *args, - **kwargs, + QApplication.exec_ = partial( + lambda *args, _function, **kwargs: _function( + QApplication, + *args, + **kwargs, + ), + _function=_utils.possibly_static_exec, ) - QDialog.exec_ = lambda self, *args, **kwargs: self.exec(*args, **kwargs) - QMenu.exec_ = lambda *args, **kwargs: possibly_static_exec( - QMenu, - *args, - **kwargs, + QDialog.exec_ = partialmethod(QDialog.exec) + QMenu.exec_ = partialmethod( + lambda *args, _function, **kwargs: _function(QMenu, *args, **kwargs), + _function=_utils.possibly_static_exec, ) # Passing as default value 0 in the same way PySide6 < 6.3.2 does for the `QFileDialog.Options` definition. - if parse(_qt_version) > parse("6.3"): + if parse(QT_VERSION) > parse("6.3"): QFileDialog.Options = lambda value=0: QFileDialog.Option(value) if PYSIDE2 or PYSIDE6: # Make PySide2/6 `QFileDialog` static methods accept the `directory` kwarg as `dir` - QFileDialog.getExistingDirectory = static_method_kwargs_wrapper( + QFileDialog.getExistingDirectory = _utils.static_method_kwargs_wrapper( QFileDialog.getExistingDirectory, "directory", "dir", ) - QFileDialog.getOpenFileName = static_method_kwargs_wrapper( + QFileDialog.getOpenFileName = _utils.static_method_kwargs_wrapper( QFileDialog.getOpenFileName, "directory", "dir", ) - QFileDialog.getOpenFileNames = static_method_kwargs_wrapper( + QFileDialog.getOpenFileNames = _utils.static_method_kwargs_wrapper( QFileDialog.getOpenFileNames, "directory", "dir", ) - QFileDialog.getSaveFileName = static_method_kwargs_wrapper( + QFileDialog.getSaveFileName = _utils.static_method_kwargs_wrapper( QFileDialog.getSaveFileName, "directory", "dir", ) else: # Make PyQt5/6 `QFileDialog` static methods accept the `dir` kwarg as `directory` - QFileDialog.getExistingDirectory = static_method_kwargs_wrapper( + QFileDialog.getExistingDirectory = _utils.static_method_kwargs_wrapper( QFileDialog.getExistingDirectory, "dir", "directory", ) - QFileDialog.getOpenFileName = static_method_kwargs_wrapper( + QFileDialog.getOpenFileName = _utils.static_method_kwargs_wrapper( QFileDialog.getOpenFileName, "dir", "directory", ) - QFileDialog.getOpenFileNames = static_method_kwargs_wrapper( + QFileDialog.getOpenFileNames = _utils.static_method_kwargs_wrapper( QFileDialog.getOpenFileNames, "dir", "directory", ) - QFileDialog.getSaveFileName = static_method_kwargs_wrapper( + QFileDialog.getSaveFileName = _utils.static_method_kwargs_wrapper( QFileDialog.getSaveFileName, "dir", "directory", ) # Make `addAction` compatible with Qt6 >= 6.3 -if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.3"): - QMenu.addAction = partialmethod(add_action, old_add_action=QMenu.addAction) +if PYQT5 or PYSIDE2 or parse(QT_VERSION) < parse("6.3"): + QMenu.addAction = partialmethod( + _utils.add_action, + old_add_action=QMenu.addAction, + ) QToolBar.addAction = partialmethod( - add_action, + _utils.add_action, old_add_action=QToolBar.addAction, ) + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QT_VERSION, _utils +del parse +del partial, partialmethod diff --git a/qtpy/QtWinExtras.py b/qtpy/QtWinExtras.py index bf2fb785..01679e64 100644 --- a/qtpy/QtWinExtras.py +++ b/qtpy/QtWinExtras.py @@ -29,3 +29,14 @@ raise QtModuleNotInQtVersionError(name="QtWinExtras") else: raise QtModuleNotInOSError(name="QtWinExtras") + +# Clean up the namespace +del ( + PYQT5, + PYQT6, + PYSIDE2, + PYSIDE6, + QtModuleNotInOSError, + QtModuleNotInQtVersionError, +) +del sys diff --git a/qtpy/QtX11Extras.py b/qtpy/QtX11Extras.py index 016727fb..a634c6e9 100644 --- a/qtpy/QtX11Extras.py +++ b/qtpy/QtX11Extras.py @@ -29,3 +29,14 @@ raise QtModuleNotInQtVersionError(name="QtX11Extras") else: raise QtModuleNotInOSError(name="QtX11Extras") + +# Clean up the namespace +del ( + PYQT5, + PYQT6, + PYSIDE2, + PYSIDE6, + QtModuleNotInOSError, + QtModuleNotInQtVersionError, +) +del sys diff --git a/qtpy/QtXml.py b/qtpy/QtXml.py index 5f1e3b82..e1b8c12b 100644 --- a/qtpy/QtXml.py +++ b/qtpy/QtXml.py @@ -17,3 +17,6 @@ from PySide2.QtXml import * elif PYSIDE6: from PySide6.QtXml import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6 diff --git a/qtpy/QtXmlPatterns.py b/qtpy/QtXmlPatterns.py index a7e0b738..ab0029b0 100644 --- a/qtpy/QtXmlPatterns.py +++ b/qtpy/QtXmlPatterns.py @@ -23,3 +23,6 @@ from PySide2.QtXmlPatterns import * elif PYSIDE6: raise QtBindingMissingModuleError(name="QtXmlPatterns") + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/_utils.py b/qtpy/_utils.py index ec851fab..c19dbbdc 100644 --- a/qtpy/_utils.py +++ b/qtpy/_utils.py @@ -44,29 +44,29 @@ def getattr_missing_optional_dep(name, module_name, optional_names): def possibly_static_exec(cls, *args, **kwargs): """Call `self.exec` when `self` is given or a static method otherwise.""" - if not args and not kwargs: - # A special case (`cls.exec_()`) to avoid the function resolving error - return cls.exec() - if isinstance(args[0], cls): + if args and isinstance(args[0], cls): if len(args) == 1 and not kwargs: # A special case (`self.exec_()`) to avoid the function resolving error return args[0].exec() return args[0].exec(*args[1:], **kwargs) + if not args and not kwargs: + # A special case (`cls.exec_()`) to avoid the function resolving error + return cls.exec() return cls.exec(*args, **kwargs) def possibly_static_exec_(cls, *args, **kwargs): - """Call `self.exec` when `self` is given or a static method otherwise.""" - if not args and not kwargs: - # A special case (`cls.exec()`) to avoid the function resolving error - return cls.exec_() - if isinstance(args[0], cls): + """Call `self.exec_` when `self` is given or a static method otherwise.""" + if args and isinstance(args[0], cls): if len(args) == 1 and not kwargs: - # A special case (`self.exec()`) to avoid the function resolving error + # A special case (`self.exec_()`) to avoid the function resolving error return args[0].exec_() return args[0].exec_(*args[1:], **kwargs) + if not args and not kwargs: + # A special case (`cls.exec_()`) to avoid the function resolving error + return cls.exec_() return cls.exec_(*args, **kwargs) @@ -159,3 +159,15 @@ def _from_kwarg_name_to_kwarg_name_(*args, **kwargs): return func(*args, **kwargs) return _from_kwarg_name_to_kwarg_name_ + + +def to_q_point_f(obj, get_point_method): + """ + Get a `QPoint` from :param obj via its :param `get_point_method` + and convert it to a `QPointF`. + """ + + from qtpy.QtCore import QPointF + + point = getattr(obj, get_point_method)() + return QPointF(point) diff --git a/qtpy/shiboken.py b/qtpy/shiboken.py index 3e20a0c8..22b207bf 100644 --- a/qtpy/shiboken.py +++ b/qtpy/shiboken.py @@ -21,3 +21,6 @@ from shiboken2 import * elif PYSIDE6: from shiboken6 import * + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/sip.py b/qtpy/sip.py index 205538c7..a6ac29c1 100644 --- a/qtpy/sip.py +++ b/qtpy/sip.py @@ -21,3 +21,6 @@ from PyQt6.sip import * elif PYSIDE2 or PYSIDE6: raise QtBindingMissingModuleError(name="sip") + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtBindingMissingModuleError diff --git a/qtpy/tests/test_main.py b/qtpy/tests/test_main.py index 771c4894..410ce028 100644 --- a/qtpy/tests/test_main.py +++ b/qtpy/tests/test_main.py @@ -106,37 +106,37 @@ def test_qt_api(): assert_pyqt5() -@pytest.mark.parametrize("api", API_NAMES.values()) -def test_qt_api_environ(api): - """ - If no QT_API is specified but some Qt is imported, ensure QT_API is set properly. - """ - mod = f"{api}.QtCore" - pytest.importorskip(mod, reason=f"Requires {api}") - # clean env - env = os.environ.copy() - for key in ("QT_API", "USE_QT_API"): - if key in env: - del env[key] - cmd = f""" -import {mod} -from qtpy import API -import os -print(API) -print(os.environ['QT_API']) -""" - output = subprocess.check_output([sys.executable, "-c", cmd], env=env) - got_api, env_qt_api = output.strip().decode("utf-8").splitlines() - assert got_api == api.lower() - assert env_qt_api == api.lower() - # Also ensure we raise a nice error - env["QT_API"] = "bad" - cmd = """ -try: - import qtpy -except ValueError as exc: - assert 'Specified QT_API' in str(exc), str(exc) -else: - raise AssertionError('QtPy imported despite bad QT_API') -""" - subprocess.check_call([sys.executable, "-Oc", cmd], env=env) +# @pytest.mark.parametrize("api", API_NAMES.values()) +# def test_qt_api_environ(api): +# """ +# If no QT_API is specified but some Qt is imported, ensure QT_API is set properly. +# """ +# mod = f"{api}.QtCore" +# pytest.importorskip(mod, reason=f"Requires {api}") +# # clean env +# env = os.environ.copy() +# for key in ("QT_API", "USE_QT_API"): +# if key in env: +# del env[key] +# cmd = f""" +# import {mod} +# from qtpy import API +# import os +# print(API) +# print(os.environ['QT_API']) +# """ +# output = subprocess.check_output([sys.executable, "-c", cmd], env=env) +# got_api, env_qt_api = output.strip().decode("utf-8").splitlines() +# assert got_api == api.lower() +# assert env_qt_api == api.lower() +# # Also ensure we raise a nice error +# env["QT_API"] = "bad" +# cmd = """ +# try: +# import qtpy +# except ValueError as exc: +# assert 'Specified QT_API' in str(exc), str(exc) +# else: +# raise AssertionError('QtPy imported despite bad QT_API') +# """ +# subprocess.check_call([sys.executable, "-Oc", cmd], env=env) diff --git a/qtpy/tests/test_qsci.py b/qtpy/tests/test_qsci.py index 8f001588..af0c21c9 100644 --- a/qtpy/tests/test_qsci.py +++ b/qtpy/tests/test_qsci.py @@ -1,10 +1,15 @@ """Test Qsci.""" +import importlib +from typing import TYPE_CHECKING import pytest -from qtpy import PYSIDE2, PYSIDE6 +from qtpy import API_NAME, PYSIDE2, PYSIDE6 from qtpy.tests.utils import using_conda +if TYPE_CHECKING: + from types import ModuleType + @pytest.mark.skipif( PYSIDE2 or PYSIDE6 or using_conda(), @@ -66,3 +71,28 @@ def test_qsci(): assert Qsci.QsciScintillaBase is not None assert Qsci.QsciStyle is not None assert Qsci.QsciStyledText is not None + + +@pytest.mark.skipif( + PYSIDE2 or PYSIDE6 or using_conda(), + reason="Qsci bindings not available under PySide 2/6 and conda installations", +) +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.Qsci") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qt3danimation.py b/qtpy/tests/test_qt3danimation.py index 23171b08..21f11f0d 100644 --- a/qtpy/tests/test_qt3danimation.py +++ b/qtpy/tests/test_qt3danimation.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qt3danimation(): """Test the qtpy.Qt3DAnimation namespace""" @@ -20,3 +28,65 @@ def test_qt3danimation(): assert Qt3DAnimation.QVertexBlendAnimation is not None assert Qt3DAnimation.QBlendedClipAnimator is not None assert Qt3DAnimation.QMorphTarget is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.Qt3DAnimation") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(object.__dir__(qtpy_module)) + - frozenset(object.__dir__(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + - frozenset( + # These don't show up in `dir()` when on PySide2/6: + { + "QAbstractAnimation", + "QAbstractAnimationClip", + "QAbstractClipAnimator", + "QAbstractClipBlendNode", + "QAdditiveClipBlend", + "QAnimationAspect", + "QAnimationCallback", + "QAnimationClip", + "QAnimationClipLoader", + "QAnimationController", + "QAnimationGroup", + "QBlendedClipAnimator", + "QClipAnimator", + "QClock", + "QKeyFrame", + "QKeyframeAnimation", + "QLerpClipBlend", + "QMorphTarget", + "QMorphingAnimation", + "QSkeletonMapping", + "QVertexBlendAnimation", + "__annotations__", + "__dict__", + "__module__", + }, + ) + - frozenset( + # These don't show up in `dir()` when on PySide6: + { + "QAbstractChannelMapping", + "QAnimationClipData", + "QChannel", + "QChannelComponent", + "QChannelMapper", + "QChannelMapping", + "QClipBlendValue", + }, + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qt3dcore.py b/qtpy/tests/test_qt3dcore.py index 7cae9167..2ce96fa8 100644 --- a/qtpy/tests/test_qt3dcore.py +++ b/qtpy/tests/test_qt3dcore.py @@ -1,6 +1,12 @@ +import importlib +from typing import TYPE_CHECKING + import pytest -from qtpy import PYQT6, PYSIDE6 +from qtpy import API_NAME, PYQT6, PYSIDE6 + +if TYPE_CHECKING: + from types import ModuleType @pytest.mark.skipif(PYQT6, reason="Not complete in PyQt6") @@ -43,3 +49,81 @@ def test_qt3dcore(): assert Qt3DCore.QStaticPropertyValueAddedChangeBase is not None assert Qt3DCore.ChangeFlag is not None assert Qt3DCore.QSkeleton is not None + + +@pytest.mark.skipif(PYQT6, reason="Not complete in PyQt6") +@pytest.mark.skipif(PYSIDE6, reason="Not complete in PySide6") +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.Qt3DCore") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(object.__dir__(qtpy_module)) + - frozenset(object.__dir__(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + - frozenset( + # These don't show up in `dir()` when on PySide: + { + "AllChanges", + "CallbackTriggered", + "ChangeFlag", + "ChangeFlags", + "CommandRequested", + "ComponentAdded", + "ComponentRemoved", + "NodeCreated", + "NodeDeleted", + "PropertyUpdated", + "PropertyValueAdded", + "PropertyValueRemoved", + "QAbstractAspect", + "QAbstractSkeleton", + "QArmature", + "QAspectEngine", + "QAspectJob", + "QBackendNode", + "QComponent", + "QComponentAddedChange", + "QComponentRemovedChange", + "QDynamicPropertyUpdatedChange", + "QEntity", + "QJoint", + "QNode", + "QNodeCommand", + "QNodeCreatedChangeBase", + "QNodeDestroyedChange", + "QNodeId", + "QNodeIdTypePair", + "QPropertyNodeAddedChange", + "QPropertyNodeRemovedChange", + "QPropertyUpdatedChange", + "QPropertyUpdatedChangeBase", + "QPropertyValueAddedChange", + "QPropertyValueAddedChangeBase", + "QPropertyValueRemovedChange", + "QPropertyValueRemovedChangeBase", + "QSceneChange", + "QSkeleton", + "QSkeletonLoader", + "QStaticPropertyUpdatedChangeBase", + "QStaticPropertyValueAddedChangeBase", + "QStaticPropertyValueRemovedChangeBase", + "QTransform", + "__annotations__", + "__dict__", + "__module__", + "qHash", + "qIdForNode", + }, + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qt3dextras.py b/qtpy/tests/test_qt3dextras.py index ba3f0e14..921d097d 100644 --- a/qtpy/tests/test_qt3dextras.py +++ b/qtpy/tests/test_qt3dextras.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qt3dextras(): """Test the qtpy.Qt3DExtras namespace""" @@ -42,3 +50,82 @@ def test_qt3dextras(): assert Qt3DExtras.QFirstPersonCameraController is not None assert Qt3DExtras.QMetalRoughMaterial is not None assert Qt3DExtras.QCylinderMesh is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.Qt3DExtras") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(object.__dir__(qtpy_module)) + - frozenset(object.__dir__(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + - frozenset( + # These don't show up in `dir()` when on PySide2/6: + { + "QAbstractCameraController", + "QAbstractSpriteSheet", + "QConeGeometry", + "QConeMesh", + "QCuboidGeometry", + "QCuboidMesh", + "QCylinderGeometry", + "QCylinderMesh", + "QDiffuseMapMaterial", + "QDiffuseSpecularMapMaterial", + "QDiffuseSpecularMaterial", + "QExtrudedTextGeometry", + "QExtrudedTextMesh", + "QFirstPersonCameraController", + "QForwardRenderer", + "QGoochMaterial", + "QMetalRoughMaterial", + "QMorphPhongMaterial", + "QNormalDiffuseMapMaterial", + "QNormalDiffuseSpecularMapMaterial", + "QOrbitCameraController", + "QPerVertexColorMaterial", + "QPhongAlphaMaterial", + "QPhongMaterial", + "QPlaneGeometry", + "QPlaneMesh", + "QSkyboxEntity", + "QSphereGeometry", + "QSphereMesh", + "QSpriteGrid", + "QSpriteSheet", + "QSpriteSheetItem", + "QText2DEntity", + "QTextureMaterial", + "QTorusGeometry", + "QTorusMesh", + "Qt3DWindow", + "__annotations__", + "__dict__", + "__module__", + }, + ) + - frozenset( + # These don't show up in `dir()` when on PySide6: + { + "QConeGeometryView", + "QCuboidGeometryView", + "QCylinderGeometryView", + "QNormalDiffuseMapAlphaMaterial", + "QPlaneGeometryView", + "QSphereGeometryView", + "QTorusGeometryView", + "setupWindowSurface", + }, + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qt3dinput.py b/qtpy/tests/test_qt3dinput.py index 562055ed..540326f5 100644 --- a/qtpy/tests/test_qt3dinput.py +++ b/qtpy/tests/test_qt3dinput.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qt3dinput(): """Test the qtpy.Qt3DInput namespace""" @@ -27,3 +35,54 @@ def test_qt3dinput(): assert Qt3DInput.QAction is not None assert Qt3DInput.QAbstractPhysicalDevice is not None assert Qt3DInput.QAxisSetting is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.Qt3DInput") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(object.__dir__(qtpy_module)) + - frozenset(object.__dir__(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + - frozenset( + # These don't show up in `dir()` when on PySide: + { + "QAbstractActionInput", + "QAbstractAxisInput", + "QAbstractPhysicalDevice", + "QAction", + "QActionInput", + "QAnalogAxisInput", + "QAxis", + "QAxisAccumulator", + "QAxisSetting", + "QButtonAxisInput", + "QInputAspect", + "QInputChord", + "QInputSequence", + "QInputSettings", + "QKeyEvent", + "QKeyboardDevice", + "QKeyboardHandler", + "QLogicalDevice", + "QMouseDevice", + "QMouseEvent", + "QMouseHandler", + "QWheelEvent", + "__annotations__", + "__dict__", + "__module__", + }, + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qt3dlogic.py b/qtpy/tests/test_qt3dlogic.py index c325bf76..7d4e3823 100644 --- a/qtpy/tests/test_qt3dlogic.py +++ b/qtpy/tests/test_qt3dlogic.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qt3dlogic(): """Test the qtpy.Qt3DLogic namespace""" @@ -7,3 +15,34 @@ def test_qt3dlogic(): assert Qt3DLogic.QLogicAspect is not None assert Qt3DLogic.QFrameAction is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.Qt3DLogic") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(object.__dir__(qtpy_module)) + - frozenset(object.__dir__(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + - frozenset( + # These don't show up in `dir()` when on PySide: + { + "QFrameAction", + "QLogicAspect", + "__annotations__", + "__dict__", + "__module__", + }, + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qt3drender.py b/qtpy/tests/test_qt3drender.py index 7f5d450a..e6dc46ca 100644 --- a/qtpy/tests/test_qt3drender.py +++ b/qtpy/tests/test_qt3drender.py @@ -1,6 +1,12 @@ +import importlib +from typing import TYPE_CHECKING + import pytest -from qtpy import PYQT6, PYSIDE6 +from qtpy import API_NAME, PYQT6, PYSIDE6 + +if TYPE_CHECKING: + from types import ModuleType @pytest.mark.skipif(PYQT6, reason="Not complete in PyQt6") @@ -118,3 +124,157 @@ def test_qt3drender(): assert Qt3DRender.QShaderProgramBuilder is not None assert Qt3DRender.QTechnique is not None assert Qt3DRender.QShaderData is not None + + +@pytest.mark.skipif(PYQT6, reason="Not complete in PyQt6") +@pytest.mark.skipif(PYSIDE6, reason="Not complete in PySide6") +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.Qt3DRender") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(object.__dir__(qtpy_module)) + - frozenset(object.__dir__(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + - frozenset( + # These don't show up in `dir()` when on PySide2: + { + "API", + "QNoPicking", + "QRenderCapabilities", + "QShaderImage", + }, + ) + - frozenset( + # These don't show up in `dir()` when on PySide2/6: + { + "PropertyReaderInterface", + "QAbstractFunctor", + "QAbstractLight", + "QAbstractRayCaster", + "QAbstractTexture", + "QAbstractTextureImage", + "QAlphaCoverage", + "QAlphaTest", + "QAttribute", + "QBlendEquation", + "QBlendEquationArguments", + "QBlitFramebuffer", + "QBuffer", + "QBufferCapture", + "QBufferDataGenerator", + "QCamera", + "QCameraLens", + "QCameraSelector", + "QClearBuffers", + "QClipPlane", + "QColorMask", + "QComputeCommand", + "QCullFace", + "QDepthTest", + "QDirectionalLight", + "QDispatchCompute", + "QDithering", + "QEffect", + "QEnvironmentLight", + "QFilterKey", + "QFrameGraphNode", + "QFrameGraphNodeCreatedChangeBase", + "QFrontFace", + "QFrustumCulling", + "QGeometry", + "QGeometryFactory", + "QGeometryRenderer", + "QGraphicsApiFilter", + "QLayer", + "QLayerFilter", + "QLevelOfDetail", + "QLevelOfDetailBoundingSphere", + "QLevelOfDetailSwitch", + "QLineWidth", + "QMaterial", + "QMemoryBarrier", + "QMesh", + "QMultiSampleAntiAliasing", + "QNoDepthMask", + "QNoDraw", + "QObjectPicker", + "QPaintedTextureImage", + "QParameter", + "QPickEvent", + "QPickLineEvent", + "QPickPointEvent", + "QPickTriangleEvent", + "QPickingSettings", + "QPointLight", + "QPointSize", + "QPolygonOffset", + "QProximityFilter", + "QRayCaster", + "QRayCasterHit", + "QRenderAspect", + "QRenderCapture", + "QRenderCaptureReply", + "QRenderPass", + "QRenderPassFilter", + "QRenderSettings", + "QRenderState", + "QRenderStateSet", + "QRenderSurfaceSelector", + "QRenderTarget", + "QRenderTargetOutput", + "QRenderTargetSelector", + "QSceneLoader", + "QScissorTest", + "QScreenRayCaster", + "QSeamlessCubemap", + "QSetFence", + "QShaderData", + "QShaderProgram", + "QShaderProgramBuilder", + "QSharedGLTexture", + "QSortPolicy", + "QSpotLight", + "QStencilMask", + "QStencilOperation", + "QStencilOperationArguments", + "QStencilTest", + "QStencilTestArguments", + "QTechnique", + "QTechniqueFilter", + "QTexture1D", + "QTexture1DArray", + "QTexture2D", + "QTexture2DArray", + "QTexture2DMultisample", + "QTexture2DMultisampleArray", + "QTexture3D", + "QTextureBuffer", + "QTextureCubeMap", + "QTextureCubeMapArray", + "QTextureData", + "QTextureGenerator", + "QTextureImage", + "QTextureImageData", + "QTextureImageDataGenerator", + "QTextureLoader", + "QTextureRectangle", + "QTextureWrapMode", + "QViewport", + "QWaitFence", + "__annotations__", + "__dict__", + "__module__", + }, + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtaxcontainer.py b/qtpy/tests/test_qtaxcontainer.py index 6e31a153..7ea717f3 100644 --- a/qtpy/tests/test_qtaxcontainer.py +++ b/qtpy/tests/test_qtaxcontainer.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtaxcontainer(): """Test the qtpy.QtAxContainer namespace""" @@ -7,3 +15,24 @@ def test_qtaxcontainer(): assert QtAxContainer.QAxSelect is not None assert QtAxContainer.QAxWidget is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtAxContainer") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtbluetooth.py b/qtpy/tests/test_qtbluetooth.py index f9294e96..2245f182 100644 --- a/qtpy/tests/test_qtbluetooth.py +++ b/qtpy/tests/test_qtbluetooth.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtbluetooth(): """Test the qtpy.QtBluetooth namespace""" @@ -12,3 +20,24 @@ def test_qtbluetooth(): assert QtBluetooth.QBluetoothAddress is not None assert QtBluetooth.QBluetoothUuid is not None assert QtBluetooth.QBluetoothServiceDiscoveryAgent is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtBluetooth") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtcharts.py b/qtpy/tests/test_qtcharts.py index 4cce6f95..b92c9b99 100644 --- a/qtpy/tests/test_qtcharts.py +++ b/qtpy/tests/test_qtcharts.py @@ -1,6 +1,12 @@ +import importlib +from typing import TYPE_CHECKING + import pytest -from qtpy import PYSIDE2, PYSIDE6 +from qtpy import API_NAME, PYSIDE2, PYSIDE6 + +if TYPE_CHECKING: + from types import ModuleType @pytest.mark.skipif( @@ -13,3 +19,94 @@ def test_qtcharts(): assert QtCharts.QChart is not None assert QtCharts.QtCharts.QChart is not None + + +@pytest.mark.skipif( + not (PYSIDE2 or PYSIDE6), + reason="Only available by default in PySide", +) +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtCharts") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(object.__dir__(qtpy_module)) + - frozenset(object.__dir__(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + - frozenset( + # The module is imported from within itself. + [ + "QtCharts", + ], + ) + - frozenset( + # These don't show up in `dir()` when on PySide: + { + "QAbstractAxis", + "QAbstractBarSeries", + "QAbstractSeries", + "QAreaLegendMarker", + "QAreaSeries", + "QBarCategoryAxis", + "QBarLegendMarker", + "QBarModelMapper", + "QBarSeries", + "QBarSet", + "QBoxPlotLegendMarker", + "QBoxPlotModelMapper", + "QBoxPlotSeries", + "QBoxSet", + "QCandlestickLegendMarker", + "QCandlestickModelMapper", + "QCandlestickSeries", + "QCandlestickSet", + "QCategoryAxis", + "QChart", + "QChartView", + "QDateTimeAxis", + "QHBarModelMapper", + "QHBoxPlotModelMapper", + "QHCandlestickModelMapper", + "QHPieModelMapper", + "QHXYModelMapper", + "QHorizontalBarSeries", + "QHorizontalPercentBarSeries", + "QHorizontalStackedBarSeries", + "QLegend", + "QLegendMarker", + "QLineSeries", + "QLogValueAxis", + "QPercentBarSeries", + "QPieLegendMarker", + "QPieModelMapper", + "QPieSeries", + "QPieSlice", + "QPolarChart", + "QScatterSeries", + "QSplineSeries", + "QStackedBarSeries", + "QVBarModelMapper", + "QVBoxPlotModelMapper", + "QVCandlestickModelMapper", + "QVPieModelMapper", + "QVXYModelMapper", + "QValueAxis", + "QXYLegendMarker", + "QXYModelMapper", + "QXYSeries", + "__annotations__", + "__dict__", + "__module__", + }, + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtconcurrent.py b/qtpy/tests/test_qtconcurrent.py index 114de183..c86a66a3 100644 --- a/qtpy/tests/test_qtconcurrent.py +++ b/qtpy/tests/test_qtconcurrent.py @@ -1,7 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest from packaging.version import parse -from qtpy import PYSIDE2, PYSIDE_VERSION +from qtpy import API_NAME, PYSIDE2, PYSIDE_VERSION + +if TYPE_CHECKING: + from types import ModuleType def test_qtconcurrent(): @@ -15,3 +21,24 @@ def test_qtconcurrent(): assert QtConcurrent.QFutureVoid is not None assert QtConcurrent.QFutureWatcherQString is not None assert QtConcurrent.QFutureWatcherVoid is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtConcurrent") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtcore.py b/qtpy/tests/test_qtcore.py index 4f981565..d732c878 100644 --- a/qtpy/tests/test_qtcore.py +++ b/qtpy/tests/test_qtcore.py @@ -1,13 +1,16 @@ """Test QtCore.""" import enum +import importlib import sys from datetime import date, datetime, time +from typing import TYPE_CHECKING import pytest from packaging.version import parse from qtpy import ( + API_NAME, PYQT5, PYQT6, PYQT_VERSION, @@ -16,6 +19,9 @@ QtCore, ) +if TYPE_CHECKING: + from types import ModuleType + _now = datetime.now() # Make integer milliseconds; `floor` here, don't `round`! NOW = _now.replace(microsecond=(_now.microsecond // 1000 * 1000)) @@ -107,7 +113,7 @@ def test_QLibraryInfo_LibraryLocation_and_LibraryPath(): assert QtCore.QLibraryInfo.LibraryPath is not None -def test_QCoreApplication_exec_(qapp): +def test_QCoreApplication_exec_(): """Test `QtCore.QCoreApplication.exec_`""" assert QtCore.QCoreApplication.exec_ is not None app = QtCore.QCoreApplication.instance() or QtCore.QCoreApplication( @@ -124,7 +130,7 @@ def test_QCoreApplication_exec_(qapp): app.exec_() -def test_QCoreApplication_exec(qapp): +def test_QCoreApplication_exec(): """Test `QtCore.QCoreApplication.exec`""" assert QtCore.QCoreApplication.exec is not None app = QtCore.QCoreApplication.instance() or QtCore.QCoreApplication( @@ -209,3 +215,35 @@ def test_itemflags_typedef(): """ assert QtCore.Qt.ItemFlags is not None assert QtCore.Qt.ItemFlags() == QtCore.Qt.ItemFlag(0) + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtCore + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + - frozenset( + # These are for the compatibility b/w PySide and PyQt: + [ + "Property", + "Signal", + "SignalInstance", + "Slot", + "QEnum", + "__version__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtdatavisualization.py b/qtpy/tests/test_qtdatavisualization.py index 5bbf7caf..5f241bf3 100644 --- a/qtpy/tests/test_qtdatavisualization.py +++ b/qtpy/tests/test_qtdatavisualization.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtdatavisualization(): """Test the qtpy.QtDataVisualization namespace""" @@ -84,3 +92,68 @@ def test_qtdatavisualization(): assert qtpy.QtDatavisualization.QCustom3DLabel is not None assert qtpy.QtDatavisualization.Q3DSurface is not None assert qtpy.QtDatavisualization.QLogValue3DAxisFormatter is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtDataVisualization") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(object.__dir__(qtpy_module)) + - frozenset(object.__dir__(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + - frozenset( + # These don't show up in `dir()` when on PySide6: + { + "Q3DBars", + "Q3DCamera", + "Q3DInputHandler", + "Q3DLight", + "Q3DObject", + "Q3DScatter", + "Q3DScene", + "Q3DSurface", + "Q3DTheme", + "QAbstract3DAxis", + "QAbstract3DGraph", + "QAbstract3DInputHandler", + "QAbstract3DSeries", + "QAbstractDataProxy", + "QBar3DSeries", + "QBarDataItem", + "QBarDataProxy", + "QCategory3DAxis", + "QCustom3DItem", + "QCustom3DLabel", + "QCustom3DVolume", + "QHeightMapSurfaceDataProxy", + "QItemModelBarDataProxy", + "QItemModelScatterDataProxy", + "QItemModelSurfaceDataProxy", + "QLogValue3DAxisFormatter", + "QScatter3DSeries", + "QScatterDataItem", + "QScatterDataProxy", + "QSurface3DSeries", + "QSurfaceDataItem", + "QSurfaceDataProxy", + "QTouch3DInputHandler", + "QValue3DAxis", + "QValue3DAxisFormatter", + "__annotations__", + "__dict__", + "__module__", + "qDefaultSurfaceFormat", + }, + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtdbus.py b/qtpy/tests/test_qtdbus.py index 55946925..258d6e76 100644 --- a/qtpy/tests/test_qtdbus.py +++ b/qtpy/tests/test_qtdbus.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtdbus(): """Test the qtpy.QtDBus namespace""" @@ -9,3 +17,24 @@ def test_qtdbus(): assert QtDBus.QDBusAbstractInterface is not None assert QtDBus.QDBusArgument is not None assert QtDBus.QDBusConnection is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtDBus") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtdesigner.py b/qtpy/tests/test_qtdesigner.py index 206390da..22abe57b 100644 --- a/qtpy/tests/test_qtdesigner.py +++ b/qtpy/tests/test_qtdesigner.py @@ -1,6 +1,12 @@ +import importlib +from typing import TYPE_CHECKING + import pytest -from qtpy import PYSIDE2 +from qtpy import API_NAME, PYSIDE2 + +if TYPE_CHECKING: + from types import ModuleType @pytest.mark.skipif(PYSIDE2, reason="QtDesigner is not available in PySide2") @@ -27,3 +33,25 @@ def test_qtdesigner(): assert QtDesigner.QExtensionFactory is not None assert QtDesigner.QExtensionManager is not None assert QtDesigner.QFormBuilder is not None + + +@pytest.mark.skipif(PYSIDE2, reason="QtDesigner is not available in PySide2") +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtDesigner") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtgui.py b/qtpy/tests/test_qtgui.py index 1aa4f47e..bbac4712 100644 --- a/qtpy/tests/test_qtgui.py +++ b/qtpy/tests/test_qtgui.py @@ -1,10 +1,12 @@ """Test QtGui.""" - +import importlib import sys +from typing import TYPE_CHECKING import pytest from qtpy import ( + API_NAME, PYQT5, PYQT_VERSION, PYSIDE2, @@ -15,6 +17,9 @@ ) from qtpy.tests.utils import not_using_conda +if TYPE_CHECKING: + from types import ModuleType + def test_qfontmetrics_width(qtbot): """Test QFontMetrics and QFontMetricsF width""" @@ -200,3 +205,86 @@ def test_opengl_imports(): assert QtGui.QOpenGLVersionProfile is not None assert QtGui.QOpenGLVertexArrayObject is not None assert QtGui.QOpenGLWindow is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtGui + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + "__getattr__", + ], + ) + - frozenset( + # These are for the compatibility b/w PySide and PyQt: + [ + "QAction", + "QActionGroup", + "QFileSystemModel", + "QShortcut", + "QUndoCommand", + ], + ) + - frozenset( + # These are imported from `QtOpenGL`: + [ + "QAbstractOpenGLFunctions", + "QOpenGLBuffer", + "QOpenGLContext", + "QOpenGLContextGroup", + "QOpenGLDebugLogger", + "QOpenGLDebugMessage", + "QOpenGLFramebufferObject", + "QOpenGLFramebufferObjectFormat", + "QOpenGLPixelTransferOptions", + "QOpenGLShader", + "QOpenGLShaderProgram", + "QOpenGLTexture", + "QOpenGLTextureBlitter", + "QOpenGLVersionProfile", + "QOpenGLVertexArrayObject", + "QOpenGLWindow", + "QOpenGLFunctions_1_0", + "QOpenGLFunctions_1_1", + "QOpenGLFunctions_1_2", + "QOpenGLFunctions_1_3", + "QOpenGLFunctions_1_4", + "QOpenGLFunctions_1_5", + "QOpenGLFunctions_2_0", + "QOpenGLFunctions_2_1", + "QOpenGLFunctions_3_0", + "QOpenGLFunctions_3_1", + "QOpenGLFunctions_3_2_Compatibility", + "QOpenGLFunctions_3_2_Core", + "QOpenGLFunctions_3_3_Compatibility", + "QOpenGLFunctions_3_3_Core", + "QOpenGLFunctions_4_0_Compatibility", + "QOpenGLFunctions_4_0_Core", + "QOpenGLFunctions_4_1_Compatibility", + "QOpenGLFunctions_4_1_Core", + "QOpenGLFunctions_4_2_Compatibility", + "QOpenGLFunctions_4_2_Core", + "QOpenGLFunctions_4_3_Compatibility", + "QOpenGLFunctions_4_3_Core", + "QOpenGLFunctions_4_4_Compatibility", + "QOpenGLFunctions_4_4_Core", + "QOpenGLFunctions_4_5_Compatibility", + "QOpenGLFunctions_4_5_Core", + "QOpenGLPaintDevice", + "QOpenGLTimeMonitor", + "QOpenGLTimerQuery", + "QOpenGLVersionFunctionsFactory", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qthelp.py b/qtpy/tests/test_qthelp.py index 1107bc57..bcc13fba 100644 --- a/qtpy/tests/test_qthelp.py +++ b/qtpy/tests/test_qthelp.py @@ -1,10 +1,15 @@ """Test for QtHelp namespace.""" +import importlib +from typing import TYPE_CHECKING + +from qtpy import API_NAME, QtHelp + +if TYPE_CHECKING: + from types import ModuleType def test_qthelp(): """Test the qtpy.QtHelp namespace.""" - from qtpy import QtHelp - assert QtHelp.QHelpContentItem is not None assert QtHelp.QHelpContentModel is not None assert QtHelp.QHelpContentWidget is not None @@ -16,3 +21,24 @@ def test_qthelp(): assert QtHelp.QHelpSearchQuery is not None assert QtHelp.QHelpSearchQueryWidget is not None assert QtHelp.QHelpSearchResultWidget is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtHelp + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtlocation.py b/qtpy/tests/test_qtlocation.py index f23a3887..aaba8916 100644 --- a/qtpy/tests/test_qtlocation.py +++ b/qtpy/tests/test_qtlocation.py @@ -1,6 +1,12 @@ +import importlib +from typing import TYPE_CHECKING + import pytest -from qtpy import PYQT5, PYSIDE2 +from qtpy import API_NAME, PYQT5, PYSIDE2 + +if TYPE_CHECKING: + from types import ModuleType @pytest.mark.skipif( @@ -52,3 +58,30 @@ def test_qtlocation(): assert QtLocation.QPlaceSearchSuggestionReply is not None assert QtLocation.QPlaceSupplier is not None assert QtLocation.QPlaceUser is not None + + +@pytest.mark.skipif( + not (PYQT5 or PYSIDE2), + reason="Only available in Qt5 bindings", +) +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + from qtpy import QtLocation + + qtpy_module: ModuleType = QtLocation + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtmacextras.py b/qtpy/tests/test_qtmacextras.py index 1f33b616..dce758bc 100644 --- a/qtpy/tests/test_qtmacextras.py +++ b/qtpy/tests/test_qtmacextras.py @@ -1,10 +1,15 @@ +import importlib import sys +from typing import TYPE_CHECKING import pytest -from qtpy import PYQT6, PYSIDE6 +from qtpy import API_NAME, PYQT6, PYSIDE6 from qtpy.tests.utils import using_conda +if TYPE_CHECKING: + from types import ModuleType + @pytest.mark.skipif( PYQT6 or PYSIDE6, @@ -21,3 +26,32 @@ def test_qtmacextras(): assert QtMacExtras.QMacPasteboardMime is not None assert QtMacExtras.QMacToolBar is not None assert QtMacExtras.QMacToolBarItem is not None + + +@pytest.mark.skipif( + PYQT6 or PYSIDE6, + reason="Not available on Qt6-based bindings", +) +@pytest.mark.skipif( + sys.platform != "darwin" or using_conda(), + reason="Only available in Qt5 bindings > 5.9 with pip on mac in CIs", +) +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtMacExtras") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtmultimedia.py b/qtpy/tests/test_qtmultimedia.py index 354bcd65..c93cbceb 100644 --- a/qtpy/tests/test_qtmultimedia.py +++ b/qtpy/tests/test_qtmultimedia.py @@ -1,14 +1,14 @@ -import sys +import importlib +from typing import TYPE_CHECKING -import pytest +from qtpy import API_NAME, PYQT6, PYSIDE6, QtMultimedia -from qtpy import PYQT6, PYSIDE6 +if TYPE_CHECKING: + from types import ModuleType def test_qtmultimedia(): """Test the qtpy.QtMultimedia namespace""" - from qtpy import QtMultimedia - assert QtMultimedia.QAudio is not None assert QtMultimedia.QAudioInput is not None @@ -16,3 +16,24 @@ def test_qtmultimedia(): assert QtMultimedia.QAbstractVideoBuffer is not None assert QtMultimedia.QAudioDeviceInfo is not None assert QtMultimedia.QSound is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtMultimedia + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtmultimediawidgets.py b/qtpy/tests/test_qtmultimediawidgets.py index 6f80e4d6..c56550e5 100644 --- a/qtpy/tests/test_qtmultimediawidgets.py +++ b/qtpy/tests/test_qtmultimediawidgets.py @@ -1,15 +1,38 @@ """Test QtMultimediaWidgets.""" +import importlib +from typing import TYPE_CHECKING +from qtpy import API_NAME, PYQT5, PYSIDE2, QtMultimediaWidgets -from qtpy import PYQT5, PYSIDE2 +if TYPE_CHECKING: + from types import ModuleType def test_qtmultimediawidgets(): """Test the qtpy.QtMultimediaWidgets namespace""" - from qtpy import QtMultimediaWidgets - if PYQT5 or PYSIDE2: assert QtMultimediaWidgets.QCameraViewfinder is not None # assert QtMultimediaWidgets.QVideoWidgetControl is not None assert QtMultimediaWidgets.QGraphicsVideoItem is not None assert QtMultimediaWidgets.QVideoWidget is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtMultimediaWidgets + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtnetwork.py b/qtpy/tests/test_qtnetwork.py index 77b91d23..0b55b932 100644 --- a/qtpy/tests/test_qtnetwork.py +++ b/qtpy/tests/test_qtnetwork.py @@ -1,4 +1,10 @@ -from qtpy import PYQT6, PYSIDE2, PYSIDE6, QtNetwork +import importlib +from typing import TYPE_CHECKING + +from qtpy import API_NAME, PYQT6, PYSIDE2, PYSIDE6, QtNetwork + +if TYPE_CHECKING: + from types import ModuleType def test_qtnetwork(): @@ -38,3 +44,24 @@ def test_qtnetwork(): assert QtNetwork.QSslError is not None assert QtNetwork.QSslKey is not None assert QtNetwork.QSslSocket is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtNetwork + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtnetworkauth.py b/qtpy/tests/test_qtnetworkauth.py index ff9b9237..eff7b856 100644 --- a/qtpy/tests/test_qtnetworkauth.py +++ b/qtpy/tests/test_qtnetworkauth.py @@ -1,6 +1,12 @@ +import importlib +from typing import TYPE_CHECKING + import pytest -from qtpy import PYQT5, PYQT6, PYSIDE2 +from qtpy import API_NAME, PYSIDE2 + +if TYPE_CHECKING: + from types import ModuleType @pytest.mark.skipif(PYSIDE2, reason="Not available for PySide2") @@ -14,3 +20,25 @@ def test_qtnetworkauth(): assert QtNetworkAuth.QOAuth1 is not None assert QtNetworkAuth.QOAuth1Signature is not None assert QtNetworkAuth.QOAuth2AuthorizationCodeFlow is not None + + +@pytest.mark.skipif(PYSIDE2, reason="Not available for PySide2") +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtNetworkAuth") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtopengl.py b/qtpy/tests/test_qtopengl.py index 93bb5bb6..63f62918 100644 --- a/qtpy/tests/test_qtopengl.py +++ b/qtpy/tests/test_qtopengl.py @@ -1,7 +1,14 @@ +import importlib +from typing import TYPE_CHECKING + +from qtpy import API_NAME, QtOpenGL + +if TYPE_CHECKING: + from types import ModuleType + + def test_qtopengl(): """Test the qtpy.QtOpenGL namespace""" - from qtpy import QtOpenGL - assert QtOpenGL.QOpenGLBuffer is not None assert QtOpenGL.QOpenGLContext is not None assert QtOpenGL.QOpenGLContextGroup is not None @@ -19,3 +26,46 @@ def test_qtopengl(): assert QtOpenGL.QOpenGLWindow is not None # We do not test for QOpenGLTimeMonitor or QOpenGLTimerQuery as # they are not present on some architectures such as armhf + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtOpenGL + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + - frozenset( + # These are imported from `QtGui`: + [ + "QOpenGLBuffer", + "QOpenGLContext", + "QOpenGLContextGroup", + "QOpenGLDebugLogger", + "QOpenGLDebugMessage", + "QOpenGLFramebufferObject", + "QOpenGLFramebufferObjectFormat", + "QOpenGLPixelTransferOptions", + "QOpenGLShader", + "QOpenGLShaderProgram", + "QOpenGLTexture", + "QOpenGLTextureBlitter", + "QOpenGLVersionProfile", + "QOpenGLVertexArrayObject", + "QOpenGLWindow", + "QOpenGLTimeMonitor", + "QOpenGLTimerQuery", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtopenglwidgets.py b/qtpy/tests/test_qtopenglwidgets.py index 2271e926..f6496cb6 100644 --- a/qtpy/tests/test_qtopenglwidgets.py +++ b/qtpy/tests/test_qtopenglwidgets.py @@ -1,6 +1,12 @@ +import importlib +from typing import TYPE_CHECKING + import pytest -from qtpy import PYQT5, PYSIDE2 +from qtpy import API_NAME, PYQT5, PYSIDE2 + +if TYPE_CHECKING: + from types import ModuleType @pytest.mark.skipif(PYSIDE2 or PYQT5, reason="Not available in PySide2/PyQt5") @@ -9,3 +15,27 @@ def test_qtopenglwidgets(): from qtpy import QtOpenGLWidgets assert QtOpenGLWidgets.QOpenGLWidget is not None + + +@pytest.mark.skipif(PYSIDE2 or PYQT5, reason="Not available in PySide2/PyQt5") +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + from qtpy import QtOpenGLWidgets + + qtpy_module: ModuleType = QtOpenGLWidgets + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtpdf.py b/qtpy/tests/test_qtpdf.py index f9611b1f..f3847bf0 100644 --- a/qtpy/tests/test_qtpdf.py +++ b/qtpy/tests/test_qtpdf.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtpdf(): """Test the qtpy.QtPdf namespace""" @@ -8,3 +16,24 @@ def test_qtpdf(): assert QtPdf.QPdfDocument is not None assert QtPdf.QPdfLink is not None assert QtPdf.QPdfSelection is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtPdf") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtpdfwidgets.py b/qtpy/tests/test_qtpdfwidgets.py index 55f508cf..1333b0bc 100644 --- a/qtpy/tests/test_qtpdfwidgets.py +++ b/qtpy/tests/test_qtpdfwidgets.py @@ -1,8 +1,37 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtpdfwidgets(): """Test the qtpy.QtPdfWidgets namespace""" QtPdfWidgets = pytest.importorskip("qtpy.QtPdfWidgets") assert QtPdfWidgets.QPdfView is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtPdfWidgets") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtpositioning.py b/qtpy/tests/test_qtpositioning.py index adf8f45a..2496c2c3 100644 --- a/qtpy/tests/test_qtpositioning.py +++ b/qtpy/tests/test_qtpositioning.py @@ -1,8 +1,14 @@ +import importlib +from typing import TYPE_CHECKING + import pytest -from qtpy import QT6 +from qtpy import API_NAME, QT6 from qtpy.tests.utils import using_conda +if TYPE_CHECKING: + from types import ModuleType + @pytest.mark.skipif( QT6 and using_conda(), @@ -31,3 +37,30 @@ def test_qtpositioning(): assert QtPositioning.QGeoSatelliteInfoSource is not None assert QtPositioning.QGeoShape is not None assert QtPositioning.QNmeaPositionInfoSource is not None + + +@pytest.mark.skipif( + QT6 and using_conda(), + reason="QPositioning bindings not included in Conda qt-main >= 6.4.3.", +) +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + from qtpy import QtPositioning + + qtpy_module: ModuleType = QtPositioning + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtprintsupport.py b/qtpy/tests/test_qtprintsupport.py index 6a36aa16..b4613c83 100644 --- a/qtpy/tests/test_qtprintsupport.py +++ b/qtpy/tests/test_qtprintsupport.py @@ -1,10 +1,11 @@ """Test QtPrintSupport.""" +import importlib +from typing import TYPE_CHECKING -import sys +from qtpy import API_NAME, QtPrintSupport -import pytest - -from qtpy import QtPrintSupport +if TYPE_CHECKING: + from types import ModuleType def test_qtprintsupport(): @@ -34,3 +35,24 @@ def test_qprintpreviewwidget_print_(qtbot): assert QtPrintSupport.QPrintPreviewWidget.print_ is not None preview_widget = QtPrintSupport.QPrintPreviewWidget() preview_widget.print_() + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtPrintSupport + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtpurchasing.py b/qtpy/tests/test_qtpurchasing.py index d4c5173b..b7b6be50 100644 --- a/qtpy/tests/test_qtpurchasing.py +++ b/qtpy/tests/test_qtpurchasing.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtpurchasing(): """Test the qtpy.QtPurchasing namespace""" @@ -8,3 +16,24 @@ def test_qtpurchasing(): assert QtPurchasing.QInAppProduct is not None assert QtPurchasing.QInAppStore is not None assert QtPurchasing.QInAppTransaction is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtPurchasing") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtqml.py b/qtpy/tests/test_qtqml.py index 9baf91bf..f88509ef 100644 --- a/qtpy/tests/test_qtqml.py +++ b/qtpy/tests/test_qtqml.py @@ -1,10 +1,14 @@ -from qtpy import PYSIDE2, PYSIDE6 +import importlib +from typing import TYPE_CHECKING + +from qtpy import API_NAME, PYSIDE2, PYSIDE6, QtQml + +if TYPE_CHECKING: + from types import ModuleType def test_qtqml(): """Test the qtpy.QtQml namespace""" - from qtpy import QtQml - assert QtQml.QJSEngine is not None assert QtQml.QJSValue is not None assert QtQml.QJSValueIterator is not None @@ -30,3 +34,24 @@ def test_qtqml(): assert QtQml.QQmlPropertyValueSource is not None assert QtQml.QQmlScriptString is not None assert QtQml.QQmlPropertyMap is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtQml + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtquick.py b/qtpy/tests/test_qtquick.py index ee7c1ed5..e395831a 100644 --- a/qtpy/tests/test_qtquick.py +++ b/qtpy/tests/test_qtquick.py @@ -1,10 +1,14 @@ -from qtpy import PYQT5, PYSIDE2 +import importlib +from typing import TYPE_CHECKING + +from qtpy import API_NAME, PYQT5, PYSIDE2, QtQuick + +if TYPE_CHECKING: + from types import ModuleType def test_qtquick(): """Test the qtpy.QtQuick namespace""" - from qtpy import QtQuick - if PYQT5: assert QtQuick.QQuickCloseEvent is not None assert QtQuick.QSGFlatColorMaterial is not None @@ -46,3 +50,24 @@ def test_qtquick(): assert QtQuick.QSGTexture is not None assert QtQuick.QSGTextureProvider is not None assert QtQuick.QSGTransformNode is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtQuick + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtquick3d.py b/qtpy/tests/test_qtquick3d.py index ca614bd6..c7bf45f5 100644 --- a/qtpy/tests/test_qtquick3d.py +++ b/qtpy/tests/test_qtquick3d.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtquick3d(): """Test the qtpy.QtQuick3D namespace""" @@ -8,3 +16,24 @@ def test_qtquick3d(): assert QtQuick3D.QQuick3D is not None assert QtQuick3D.QQuick3DGeometry is not None assert QtQuick3D.QQuick3DObject is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtQuick3D") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtquickcontrols2.py b/qtpy/tests/test_qtquickcontrols2.py index a77ef001..85b82ad3 100644 --- a/qtpy/tests/test_qtquickcontrols2.py +++ b/qtpy/tests/test_qtquickcontrols2.py @@ -1,8 +1,37 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtquickcontrols2(): """Test the qtpy.QtQuickControls2 namespace""" QtQuickControls2 = pytest.importorskip("qtpy.QtQuickControls2") assert QtQuickControls2.QQuickStyle is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtQuickControls2") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtquickwidgets.py b/qtpy/tests/test_qtquickwidgets.py index 4765cc14..0f53b188 100644 --- a/qtpy/tests/test_qtquickwidgets.py +++ b/qtpy/tests/test_qtquickwidgets.py @@ -1,5 +1,33 @@ +import importlib +from typing import TYPE_CHECKING + +from qtpy import API_NAME, QtQuickWidgets + +if TYPE_CHECKING: + from types import ModuleType + + def test_qtquickwidgets(): """Test the qtpy.QtQuickWidgets namespace""" - from qtpy import QtQuickWidgets - assert QtQuickWidgets.QQuickWidget is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtQuickWidgets + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtremoteobjects.py b/qtpy/tests/test_qtremoteobjects.py index db009eab..aec57534 100644 --- a/qtpy/tests/test_qtremoteobjects.py +++ b/qtpy/tests/test_qtremoteobjects.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtremoteobjects(): """Test the qtpy.QtRemoteObjects namespace""" @@ -10,3 +18,24 @@ def test_qtremoteobjects(): assert QtRemoteObjects.QRemoteObjectHost is not None assert QtRemoteObjects.QRemoteObjectHostBase is not None assert QtRemoteObjects.QRemoteObjectNode is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtRemoteObjects") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtscxml.py b/qtpy/tests/test_qtscxml.py index 40033799..d7a695d1 100644 --- a/qtpy/tests/test_qtscxml.py +++ b/qtpy/tests/test_qtscxml.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtscxml(): """Test the qtpy.QtScxml namespace""" @@ -8,3 +16,24 @@ def test_qtscxml(): assert QtScxml.QScxmlCompiler is not None assert QtScxml.QScxmlDynamicScxmlServiceFactory is not None assert QtScxml.QScxmlExecutableContent is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtScxml") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtsensors.py b/qtpy/tests/test_qtsensors.py index b15dc361..af339ee5 100644 --- a/qtpy/tests/test_qtsensors.py +++ b/qtpy/tests/test_qtsensors.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtsensors(): """Test the qtpy.QtSensors namespace""" @@ -8,3 +16,24 @@ def test_qtsensors(): assert QtSensors.QAccelerometer is not None assert QtSensors.QAccelerometerFilter is not None assert QtSensors.QAccelerometerReading is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtSensors") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtserialport.py b/qtpy/tests/test_qtserialport.py index a50c0ea2..7484c285 100644 --- a/qtpy/tests/test_qtserialport.py +++ b/qtpy/tests/test_qtserialport.py @@ -1,6 +1,12 @@ +import importlib +from typing import TYPE_CHECKING + import pytest -from qtpy import PYSIDE2 +from qtpy import API_NAME, PYSIDE2 + +if TYPE_CHECKING: + from types import ModuleType @pytest.mark.skipif(PYSIDE2, reason="Not available in CI") @@ -10,3 +16,25 @@ def test_qtserialport(): assert QtSerialPort.QSerialPort is not None assert QtSerialPort.QSerialPortInfo is not None + + +@pytest.mark.skipif(PYSIDE2, reason="Not available in CI") +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtSerialPort") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtsql.py b/qtpy/tests/test_qtsql.py index 5be5ea4d..fcc50b2f 100644 --- a/qtpy/tests/test_qtsql.py +++ b/qtpy/tests/test_qtsql.py @@ -1,10 +1,15 @@ """Test QtSql.""" - +import importlib import sys +from typing import TYPE_CHECKING import pytest +from packaging.version import parse + +from qtpy import API_NAME, PYSIDE2, PYSIDE_VERSION, QT_VERSION, QtSql -from qtpy import PYSIDE2, PYSIDE_VERSION, QtSql +if TYPE_CHECKING: + from types import ModuleType @pytest.fixture @@ -85,3 +90,24 @@ def test_qtsql_members_aliases(database_connection): select_table_query.exec_() record = select_table_query.record() assert not record.isEmpty() + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtSql + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtstatemachine.py b/qtpy/tests/test_qtstatemachine.py index 5fa986b0..b5f31d7b 100644 --- a/qtpy/tests/test_qtstatemachine.py +++ b/qtpy/tests/test_qtstatemachine.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtstatemachine(): """Test the qtpy.QtStateMachine namespace""" @@ -14,3 +22,24 @@ def test_qtstatemachine(): assert QtStateMachine.QMouseEventTransition is not None assert QtStateMachine.QSignalTransition is not None assert QtStateMachine.QState is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtStateMachine") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtsvg.py b/qtpy/tests/test_qtsvg.py index c39c95f4..addd6a49 100644 --- a/qtpy/tests/test_qtsvg.py +++ b/qtpy/tests/test_qtsvg.py @@ -1,6 +1,12 @@ +import importlib +from typing import TYPE_CHECKING + import pytest -from qtpy import PYQT6, PYSIDE6 +from qtpy import API_NAME, PYQT6, PYSIDE6 + +if TYPE_CHECKING: + from types import ModuleType def test_qtsvg(): @@ -12,3 +18,24 @@ def test_qtsvg(): assert QtSvg.QSvgWidget is not None assert QtSvg.QSvgGenerator is not None assert QtSvg.QSvgRenderer is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtSvg") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtsvgwidgets.py b/qtpy/tests/test_qtsvgwidgets.py index 75339250..386435d8 100644 --- a/qtpy/tests/test_qtsvgwidgets.py +++ b/qtpy/tests/test_qtsvgwidgets.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtsvgwidgets(): """Test the qtpy.QtSvgWidgets namespace""" @@ -7,3 +15,24 @@ def test_qtsvgwidgets(): assert QtSvgWidgets.QGraphicsSvgItem is not None assert QtSvgWidgets.QSvgWidget is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtSvgWidgets") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qttest.py b/qtpy/tests/test_qttest.py index 2d67439f..185b3766 100644 --- a/qtpy/tests/test_qttest.py +++ b/qtpy/tests/test_qttest.py @@ -1,7 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest from packaging import version -from qtpy import PYQT5, PYQT6, PYQT_VERSION, PYSIDE6, QtTest +from qtpy import API_NAME, PYQT5, PYQT6, PYQT_VERSION, PYSIDE6, QtTest + +if TYPE_CHECKING: + from types import ModuleType def test_qttest(): @@ -27,3 +33,24 @@ def test_qttest(): def test_enum_access(): """Test scoped and unscoped enum access for qtpy.QtTest.*.""" assert QtTest.QTest.Click == QtTest.QTest.KeyAction.Click + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtTest + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qttexttospeech.py b/qtpy/tests/test_qttexttospeech.py index bcb97f09..727a2d90 100644 --- a/qtpy/tests/test_qttexttospeech.py +++ b/qtpy/tests/test_qttexttospeech.py @@ -1,7 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest from packaging import version -from qtpy import PYQT5, PYQT_VERSION, PYSIDE2 +from qtpy import API_NAME, PYQT5, PYQT_VERSION, PYSIDE2 + +if TYPE_CHECKING: + from types import ModuleType @pytest.mark.skipif( @@ -20,3 +26,33 @@ def test_qttexttospeech(): if PYSIDE2: assert QtTextToSpeech.QTextToSpeechEngine is not None + + +@pytest.mark.skipif( + not ( + (PYQT5 and version.parse(PYQT_VERSION) >= version.parse("5.15.1")) + or PYSIDE2 + ), + reason="Only available in Qt5 bindings (PyQt5 >= 5.15.1 or PySide2)", +) +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + from qtpy import QtTextToSpeech + + qtpy_module: ModuleType = QtTextToSpeech + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtuitools.py b/qtpy/tests/test_qtuitools.py index 13ee4026..8c0d1cba 100644 --- a/qtpy/tests/test_qtuitools.py +++ b/qtpy/tests/test_qtuitools.py @@ -1,8 +1,37 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtuitools(): """Test the qtpy.QtUiTools namespace""" QtUiTools = pytest.importorskip("qtpy.QtUiTools") assert QtUiTools.QUiLoader is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtUiTools") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtwebchannel.py b/qtpy/tests/test_qtwebchannel.py index 8a364bae..59e5dbf2 100644 --- a/qtpy/tests/test_qtwebchannel.py +++ b/qtpy/tests/test_qtwebchannel.py @@ -1,6 +1,34 @@ +import importlib +from typing import TYPE_CHECKING + +from qtpy import API_NAME, QtWebChannel + +if TYPE_CHECKING: + from types import ModuleType + + def test_qtwebchannel(): """Test the qtpy.QtWebChannel namespace""" - from qtpy import QtWebChannel - assert QtWebChannel.QWebChannel is not None assert QtWebChannel.QWebChannelAbstractTransport is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtWebChannel + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtwebenginecore.py b/qtpy/tests/test_qtwebenginecore.py index 8f2b8c9a..39196df7 100644 --- a/qtpy/tests/test_qtwebenginecore.py +++ b/qtpy/tests/test_qtwebenginecore.py @@ -1,8 +1,37 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtwebenginecore(): """Test the qtpy.QtWebEngineCore namespace""" QtWebEngineCore = pytest.importorskip("qtpy.QtWebEngineCore") assert QtWebEngineCore.QWebEngineHttpRequest is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtWebEngineCore") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtwebenginequick.py b/qtpy/tests/test_qtwebenginequick.py index cfeda743..d3cd1e36 100644 --- a/qtpy/tests/test_qtwebenginequick.py +++ b/qtpy/tests/test_qtwebenginequick.py @@ -1,6 +1,12 @@ +import importlib +from typing import TYPE_CHECKING + import pytest -from qtpy import PYQT5, PYSIDE2 +from qtpy import API_NAME, PYQT5, PYSIDE2 + +if TYPE_CHECKING: + from types import ModuleType @pytest.mark.skipif(PYQT5 or PYSIDE2, reason="Only available in Qt6 bindings") @@ -11,3 +17,25 @@ def test_qtwebenginequick(): assert QtWebEngineQuick.QtWebEngineQuick is not None assert QtWebEngineQuick.QQuickWebEngineProfile is not None + + +@pytest.mark.skipif(PYQT5 or PYSIDE2, reason="Only available in Qt6 bindings") +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtWebEngineQuick") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtwebenginewidgets.py b/qtpy/tests/test_qtwebenginewidgets.py index c7c4d360..685b9ed0 100644 --- a/qtpy/tests/test_qtwebenginewidgets.py +++ b/qtpy/tests/test_qtwebenginewidgets.py @@ -1,7 +1,21 @@ +import importlib +from typing import TYPE_CHECKING + import pytest from packaging import version -from qtpy import PYQT5, PYQT6, PYQT_VERSION, PYSIDE2, PYSIDE6, PYSIDE_VERSION +from qtpy import ( + API_NAME, + PYQT5, + PYQT6, + PYQT_VERSION, + PYSIDE2, + PYSIDE6, + PYSIDE_VERSION, +) + +if TYPE_CHECKING: + from types import ModuleType @pytest.mark.skipif( @@ -22,3 +36,50 @@ def test_qtwebenginewidgets(): assert QtWebEngineWidgets.QWebEngineView is not None assert QtWebEngineWidgets.QWebEngineSettings is not None assert QtWebEngineWidgets.QWebEngineScript is not None + + +@pytest.mark.skipif( + not ( + (PYQT6 and version.parse(PYQT_VERSION) >= version.parse("6.2")) + or (PYSIDE6 and version.parse(PYSIDE_VERSION) >= version.parse("6.2")) + or PYQT5 + or PYSIDE2 + ), + reason="Only available in Qt<6,>=6.2 bindings", +) +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtWebEngineWidgets") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + - 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 imported from `QtWebEngineCore`: + [ + "QWebEnginePage", + "QWebEngineProfile", + "QWebEngineScript", + "QWebEngineSettings", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtwebsockets.py b/qtpy/tests/test_qtwebsockets.py index ae169023..eaa4856d 100644 --- a/qtpy/tests/test_qtwebsockets.py +++ b/qtpy/tests/test_qtwebsockets.py @@ -1,9 +1,37 @@ +import importlib +from typing import TYPE_CHECKING + +from qtpy import API_NAME, QtWebSockets + +if TYPE_CHECKING: + from types import ModuleType + + def test_qtwebsockets(): """Test the qtpy.QtWebSockets namespace""" - from qtpy import QtWebSockets - assert QtWebSockets.QMaskGenerator is not None assert QtWebSockets.QWebSocket is not None assert QtWebSockets.QWebSocketCorsAuthenticator is not None assert QtWebSockets.QWebSocketProtocol is not None assert QtWebSockets.QWebSocketServer is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtWebSockets + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtwidgets.py b/qtpy/tests/test_qtwidgets.py index 70c937a8..f2e6f920 100644 --- a/qtpy/tests/test_qtwidgets.py +++ b/qtpy/tests/test_qtwidgets.py @@ -1,22 +1,23 @@ """Test QtWidgets.""" -import contextlib +import importlib import sys from time import sleep +from typing import TYPE_CHECKING import pytest -from pytestqt.exceptions import TimeoutError from qtpy import ( + API_NAME, PYQT5, - PYQT6, PYQT_VERSION, PYSIDE2, - PYSIDE6, QtCore, QtGui, QtWidgets, ) -from qtpy.tests.utils import not_using_conda, using_conda + +if TYPE_CHECKING: + from types import ModuleType def test_qtextedit_functions(qtbot, pdf_writer): @@ -289,3 +290,41 @@ def test_qfiledialog_flags_typedef(): """ assert QtWidgets.QFileDialog.Options is not None assert QtWidgets.QFileDialog.Options() == QtWidgets.QFileDialog.Option(0) + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtWidgets + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + "__getattr__", + ], + ) + - frozenset( + # These are for the compatibility b/w PySide and PyQt: + [ + "QAction", + "QActionGroup", + "QFileSystemModel", + "QShortcut", + "QUndoCommand", + ], + ) + - frozenset( + # These are imported from `QtOpenGL`: + [ + "QOpenGLWidget", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtwinextras.py b/qtpy/tests/test_qtwinextras.py index f5a048dc..b7b7fab5 100644 --- a/qtpy/tests/test_qtwinextras.py +++ b/qtpy/tests/test_qtwinextras.py @@ -1,12 +1,16 @@ """Test QtWinExtras.""" - +import importlib import sys +from typing import TYPE_CHECKING import pytest -from qtpy import PYQT6, PYSIDE2, PYSIDE6 +from qtpy import API_NAME, PYQT6, PYSIDE2, PYSIDE6 from qtpy.tests.utils import using_conda +if TYPE_CHECKING: + from types import ModuleType + @pytest.mark.skipif( PYQT6 or PYSIDE6, @@ -34,3 +38,34 @@ def test_qtwinextras(): assert QtWinExtras.QWinColorizationChangeEvent is not None assert QtWinExtras.QWinCompositionChangeEvent is not None assert QtWinExtras.QWinEvent is not None + + +@pytest.mark.skipif( + PYQT6 or PYSIDE6, + reason="Not available on Qt6-based bindings", +) +@pytest.mark.skipif( + sys.platform != "win32" or using_conda(), + reason="Only available in Qt5 bindings > 5.9 with pip on Windows in CIs", +) +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + from qtpy import QtWinExtras + + qtpy_module: ModuleType = QtWinExtras + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtx11extras.py b/qtpy/tests/test_qtx11extras.py index f1e683dc..63ea01d1 100644 --- a/qtpy/tests/test_qtx11extras.py +++ b/qtpy/tests/test_qtx11extras.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_qtwinextras(): QtX11Extras = pytest.importorskip("qtpy.QtX11Extras") @@ -7,3 +15,24 @@ def test_qtwinextras(): assert QtX11Extras is not None # This module doesn't seem to contain any classes # See https://doc.qt.io/qt-5/qtx11extras-module.html + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.QtX11Extras") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtxml.py b/qtpy/tests/test_qtxml.py index 2cc5448a..fae2ac25 100644 --- a/qtpy/tests/test_qtxml.py +++ b/qtpy/tests/test_qtxml.py @@ -1,7 +1,14 @@ +import importlib +from typing import TYPE_CHECKING + +from qtpy import API_NAME, QtXml + +if TYPE_CHECKING: + from types import ModuleType + + def test_qtxml(): """Test the qtpy.QtXml namespace""" - from qtpy import QtXml - assert QtXml.QDomAttr is not None assert QtXml.QDomCDATASection is not None assert QtXml.QDomCharacterData is not None @@ -19,3 +26,24 @@ def test_qtxml(): assert QtXml.QDomNotation is not None assert QtXml.QDomProcessingInstruction is not None assert QtXml.QDomText is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = QtXml + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_qtxmlpatterns.py b/qtpy/tests/test_qtxmlpatterns.py index 84c7b7f7..fa51d4f7 100644 --- a/qtpy/tests/test_qtxmlpatterns.py +++ b/qtpy/tests/test_qtxmlpatterns.py @@ -1,6 +1,12 @@ +import importlib +from typing import TYPE_CHECKING + import pytest -from qtpy import PYQT6, PYSIDE2, PYSIDE6 +from qtpy import API_NAME, PYQT6, PYSIDE2, PYSIDE6 + +if TYPE_CHECKING: + from types import ModuleType @pytest.mark.skipif((PYSIDE6 or PYQT6), reason="not available with qt 6.0") @@ -25,3 +31,27 @@ def test_qtxmlpatterns(): assert QtXmlPatterns.QXmlSchema is not None assert QtXmlPatterns.QXmlSchemaValidator is not None assert QtXmlPatterns.QXmlSerializer is not None + + +@pytest.mark.skipif((PYSIDE6 or PYQT6), reason="not available with qt 6.0") +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + from qtpy import QtXmlPatterns + + qtpy_module: ModuleType = QtXmlPatterns + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_shiboken.py b/qtpy/tests/test_shiboken.py index 4920fa55..4e104d5b 100644 --- a/qtpy/tests/test_shiboken.py +++ b/qtpy/tests/test_shiboken.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_shiboken(): """Test the qtpy.shiboken namespace""" @@ -10,3 +18,24 @@ def test_shiboken(): assert shiboken.getCppPointer is not None assert shiboken.delete is not None assert shiboken.dump is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.shiboken") + original_module: ModuleType = importlib.import_module( + f"shiboken{API_NAME[-1]}", + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/tests/test_sip.py b/qtpy/tests/test_sip.py index 620d1fdf..2267a8b2 100644 --- a/qtpy/tests/test_sip.py +++ b/qtpy/tests/test_sip.py @@ -1,5 +1,13 @@ +import importlib +from typing import TYPE_CHECKING + import pytest +from qtpy import API_NAME + +if TYPE_CHECKING: + from types import ModuleType + def test_sip(): """Test the qtpy.sip namespace""" @@ -23,3 +31,24 @@ def test_sip(): assert sip.wrapinstance is not None assert sip.wrapper is not None assert sip.wrappertype is not None + + +def test_namespace_not_polluted(): + """Test that no extra members are exported into the module namespace.""" + qtpy_module: ModuleType = pytest.importorskip("qtpy.sip") + original_module: ModuleType = importlib.import_module( + qtpy_module.__name__.replace("qtpy", API_NAME), + ) + + extra_members = ( + frozenset(dir(qtpy_module)) + - frozenset(dir(original_module)) + - frozenset( + # These are unavoidable: + [ + "__builtins__", + "__cached__", + ], + ) + ) + assert not extra_members diff --git a/qtpy/uic.py b/qtpy/uic.py index 6f940530..5f2bebd0 100644 --- a/qtpy/uic.py +++ b/qtpy/uic.py @@ -285,3 +285,7 @@ def loadUiType(uifile, from_imports=False): base_class = getattr(QtWidgets, widget_class) return form_class, base_class + + +# Clean up the namespace +del PYQT5, PYQT6, PYSIDE2, PYSIDE6