Files
openide/python/helpers/pydev/_pydev_bundle/pydev_monkey_qt.py
Egor Eliseev 831aedfedd Fix python debugger tests
GitOrigin-RevId: c8e51b9e3dcdc40b88af67b24fc77bff06773d82
2023-02-07 12:12:59 +00:00

254 lines
8.8 KiB
Python

from __future__ import nested_scopes
from _pydev_imps._pydev_saved_modules import threading
from _pydevd_bundle.pydevd_constants import IS_PY38
import os
def set_trace_in_qt():
from _pydevd_bundle.pydevd_comm import get_global_debugger
debugger = get_global_debugger()
if debugger is not None:
threading.current_thread() # Create the dummy thread for qt.
debugger.enable_tracing()
_patched_qt = False
def patch_qt(qt_support_mode):
'''
This method patches qt (PySide, PyQt4, PyQt5) so that we have hooks to set the tracing for QThread.
'''
if not qt_support_mode:
return
if qt_support_mode is True or qt_support_mode == 'True':
# do not break backward compatibility
qt_support_mode = 'auto'
if qt_support_mode == 'auto':
qt_support_mode = os.getenv('PYDEVD_PYQT_MODE', 'auto')
# Avoid patching more than once
global _patched_qt
if _patched_qt:
return
_patched_qt = True
if qt_support_mode == 'auto':
patch_qt_on_import = None
try:
if IS_PY38:
raise ImportError
import PySide6
qt_support_mode = 'pyside6'
except:
try:
# PY-50959
# Problem:
# 1. We have Python 3.8;
# 2. PyQt compatible = Auto or PySide2;
# 3. We try to import numpy, we get "AttributeError: module 'numpy.core' has no attribute 'numerictypes'"
#
# Solution:
# We decided to turn off patching for PySide2 if we have Python 3.8
# Here we skip 'import PySide2' and keep trying to import another qt libraries
if IS_PY38:
raise ImportError
import PySide2 # @UnresolvedImport @UnusedImport
qt_support_mode = 'pyside2'
except:
try:
import Pyside # @UnresolvedImport @UnusedImport
qt_support_mode = 'pyside'
except:
try:
import PyQt6 # @UnresolvedImport @UnusedImport
qt_support_mode = 'pyqt6'
except:
try:
import PyQt5 # @UnresolvedImport @UnusedImport
qt_support_mode = 'pyqt5'
except:
try:
import PyQt4 # @UnresolvedImport @UnusedImport
qt_support_mode = 'pyqt4'
except:
return
if qt_support_mode == 'pyside6':
if IS_PY38:
return
try:
import PySide6.QtCore # @UnresolvedImport
_internal_patch_qt(PySide6.QtCore, qt_support_mode)
except:
return
elif qt_support_mode == 'pyside2':
# PY-50959
# We can get here only if PyQt compatible = PySide2, in this case we should return
# See comment above about PY-50959
if IS_PY38:
return
try:
import PySide2.QtCore # @UnresolvedImport
_internal_patch_qt(PySide2.QtCore, qt_support_mode)
except:
return
elif qt_support_mode == 'pyside':
try:
import PySide.QtCore # @UnresolvedImport
_internal_patch_qt(PySide.QtCore, qt_support_mode)
except:
return
elif qt_support_mode == 'pyqt6':
try:
import PyQt6.QtCore # @UnresolvedImport
_internal_patch_qt(PyQt6.QtCore)
except:
return
elif qt_support_mode == 'pyqt5':
try:
import PyQt5.QtCore # @UnresolvedImport
_internal_patch_qt(PyQt5.QtCore)
except:
return
elif qt_support_mode == 'pyqt4':
# Ok, we have an issue here:
# PyDev-452: Selecting PyQT API version using sip.setapi fails in debug mode
# http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
# Mostly, if the user uses a different API version (i.e.: v2 instead of v1),
# that has to be done before importing PyQt4 modules (PySide/PyQt5 don't have this issue
# as they only implements v2).
patch_qt_on_import = 'PyQt4'
def get_qt_core_module():
import PyQt4.QtCore # @UnresolvedImport
return PyQt4.QtCore
_patch_import_to_patch_pyqt_on_import(patch_qt_on_import, get_qt_core_module)
else:
raise ValueError('Unexpected qt support mode: %s' % (qt_support_mode,))
def _patch_import_to_patch_pyqt_on_import(patch_qt_on_import, get_qt_core_module):
# I don't like this approach very much as we have to patch __import__, but I like even less
# asking the user to configure something in the client side...
# So, our approach is to patch PyQt4 right before the user tries to import it (at which
# point he should've set the sip api version properly already anyways).
dotted = patch_qt_on_import + '.'
original_import = __import__
from _pydev_imps._pydev_sys_patch import patch_sys_module, patch_reload, cancel_patches_in_sys_module
patch_sys_module()
patch_reload()
def patched_import(name, *args, **kwargs):
if patch_qt_on_import == name or name.startswith(dotted):
builtins.__import__ = original_import
cancel_patches_in_sys_module()
_internal_patch_qt(get_qt_core_module()) # Patch it only when the user would import the qt module
return original_import(name, *args, **kwargs)
import sys
if sys.version_info[0] >= 3:
import builtins # Py3
else:
import __builtin__ as builtins
builtins.__import__ = patched_import
def _internal_patch_qt(QtCore, qt_support_mode='auto'):
_original_thread_init = QtCore.QThread.__init__
_original_runnable_init = QtCore.QRunnable.__init__
_original_QThread = QtCore.QThread
class FuncWrapper:
def __init__(self, original):
self._original = original
def __call__(self, *args, **kwargs):
set_trace_in_qt()
return self._original(*args, **kwargs)
class StartedSignalWrapper(QtCore.QObject): # Wrapper for the QThread.started signal
try:
_signal = QtCore.Signal() # @UndefinedVariable
except:
_signal = QtCore.pyqtSignal() # @UndefinedVariable
def __init__(self, thread, original_started):
QtCore.QObject.__init__(self)
self.thread = thread
self.original_started = original_started
if qt_support_mode in ('pyside', 'pyside2', 'pyside6'):
self._signal = original_started
else:
self._signal.connect(self._on_call)
self.original_started.connect(self._signal)
def connect(self, func, *args, **kwargs):
_is_signal_instance = False
if hasattr(QtCore, 'SignalInstance'):
_is_signal_instance = isinstance(func, QtCore.SignalInstance)
if qt_support_mode in ('pyside', 'pyside2', 'pyside6') and not _is_signal_instance:
return self._signal.connect(FuncWrapper(func), *args, **kwargs)
else:
return self._signal.connect(func, *args, **kwargs)
def disconnect(self, *args, **kwargs):
return self._signal.disconnect(*args, **kwargs)
def emit(self, *args, **kwargs):
return self._signal.emit(*args, **kwargs)
def _on_call(self, *args, **kwargs):
set_trace_in_qt()
class ThreadWrapper(QtCore.QThread): # Wrapper for QThread
def __init__(self, *args, **kwargs):
_original_thread_init(self, *args, **kwargs)
# In PyQt5 the program hangs when we try to call original run method of QThread class.
# So we need to distinguish instances of QThread class and instances of QThread inheritors.
if self.__class__.run == _original_QThread.run:
self.run = self._exec_run
else:
self._original_run = self.run
self.run = self._new_run
self._original_started = self.started
self.started = StartedSignalWrapper(self, self.started)
def _exec_run(self):
set_trace_in_qt()
self.exec_()
return None
def _new_run(self):
set_trace_in_qt()
return self._original_run()
class RunnableWrapper(QtCore.QRunnable): # Wrapper for QRunnable
def __init__(self, *args):
_original_runnable_init(self, *args)
self._original_run = self.run
self.run = self._new_run
def _new_run(self):
set_trace_in_qt()
return self._original_run()
QtCore.QThread = ThreadWrapper
QtCore.QRunnable = RunnableWrapper