Skip to content

Commit cf0d684

Browse files
committed
Additional fixes/tests for supporting guiEventLoop.
1 parent 043b11f commit cf0d684

File tree

11 files changed

+241
-42
lines changed

11 files changed

+241
-42
lines changed

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_utils.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import signal
88
import os
99
import ctypes
10+
from importlib import import_module
1011

1112
try:
1213
from urllib import quote
@@ -506,3 +507,28 @@ def _compute_get_attr_slow(self, diff, cls, attr_name):
506507
pass
507508
return 'pydevd warning: Getting attribute %s.%s was slow (took %.2fs)\n' % (cls, attr_name, diff)
508509

510+
511+
def import_attr_from_module(import_with_attr_access):
512+
if '.' not in import_with_attr_access:
513+
# We need at least one '.' (we don't support just the module import, we need the attribute access too).
514+
raise ImportError('Unable to import module with attr access: %s' % (import_with_attr_access,))
515+
516+
module_name, attr_name = import_with_attr_access.rsplit('.', 1)
517+
518+
while True:
519+
try:
520+
mod = import_module(module_name)
521+
except ImportError:
522+
if '.' not in module_name:
523+
raise ImportError('Unable to import module with attr access: %s' % (import_with_attr_access,))
524+
525+
module_name, new_attr_part = module_name.rsplit('.', 1)
526+
attr_name = new_attr_part + '.' + attr_name
527+
else:
528+
# Ok, we got the base module, now, get the attribute we need.
529+
try:
530+
for attr in attr_name.split('.'):
531+
mod = getattr(mod, attr)
532+
return mod
533+
except:
534+
raise ImportError('Unable to import module with attr access: %s' % (import_with_attr_access,))

src/debugpy/_vendored/pydevd/pydev_ipython/inputhook.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ def enable_gui(gui=None, app=None):
535535
if gui is None or gui == '':
536536
gui_hook = clear_inputhook
537537
else:
538-
e = "Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys())
538+
e = "Invalid GUI request %r, valid ones are:%s" % (gui, list(guis.keys()))
539539
raise ValueError(e)
540540
return gui_hook(app)
541541

src/debugpy/_vendored/pydevd/pydev_ipython/inputhookqt5.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,39 @@
2121

2222
import threading
2323

24-
2524
from pydev_ipython.qt_for_kernel import QtCore, QtGui
2625
from pydev_ipython.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready
2726

27+
2828
# To minimise future merging complexity, rather than edit the entire code base below
2929
# we fake InteractiveShell here
3030
class InteractiveShell:
3131
_instance = None
32+
3233
@classmethod
3334
def instance(cls):
3435
if cls._instance is None:
3536
cls._instance = cls()
3637
return cls._instance
38+
3739
def set_hook(self, *args, **kwargs):
3840
# We don't consider the pre_prompt_hook because we don't have
3941
# KeyboardInterrupts to consider since we are running under PyDev
4042
pass
4143

42-
4344
#-----------------------------------------------------------------------------
4445
# Module Globals
4546
#-----------------------------------------------------------------------------
4647

48+
4749
got_kbdint = False
4850
sigint_timer = None
4951

5052
#-----------------------------------------------------------------------------
5153
# Code
5254
#-----------------------------------------------------------------------------
5355

56+
5457
def create_inputhook_qt5(mgr, app=None):
5558
"""Create an input hook for running the Qt5 application event loop.
5659
@@ -107,7 +110,7 @@ def inputhook_qt5():
107110
try:
108111
allow_CTRL_C()
109112
app = QtCore.QCoreApplication.instance()
110-
if not app: # shouldn't happen, but safer if it happens anyway...
113+
if not app: # shouldn't happen, but safer if it happens anyway...
111114
return 0
112115
app.processEvents(QtCore.QEventLoop.AllEvents, 300)
113116
if not stdin_ready():
@@ -159,13 +162,12 @@ def inputhook_qt5():
159162
pid = os.getpid()
160163
if(not sigint_timer):
161164
sigint_timer = threading.Timer(.01, os.kill,
162-
args=[pid, signal.SIGINT] )
165+
args=[pid, signal.SIGINT])
163166
sigint_timer.start()
164167
else:
165168
print("\nKeyboardInterrupt - Ctrl-C again for new prompt")
166169

167-
168-
except: # NO exceptions are allowed to escape from a ctypes callback
170+
except: # NO exceptions are allowed to escape from a ctypes callback
169171
ignore_CTRL_C()
170172
from traceback import print_exc
171173
print_exc()

src/debugpy/_vendored/pydevd/pydev_ipython/qt_for_kernel.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@
3535
import sys
3636

3737
from pydev_ipython.version import check_version
38-
from pydev_ipython.qt_loaders import (load_qt, QT_API_PYSIDE,
38+
from pydev_ipython.qt_loaders import (load_qt, QT_API_PYSIDE, QT_API_PYSIDE2,
3939
QT_API_PYQT, QT_API_PYQT_DEFAULT,
4040
loaded_api, QT_API_PYQT5)
4141

42-
#Constraints placed on an imported matplotlib
42+
43+
# Constraints placed on an imported matplotlib
4344
def matplotlib_options(mpl):
4445
if mpl is None:
4546
return
@@ -70,7 +71,6 @@ def matplotlib_options(mpl):
7071
raise ImportError("unhandled value for backend.qt5 from matplotlib: %r" %
7172
mpqt)
7273

73-
7474
# Fallback without checking backend (previous code)
7575
mpqt = mpl.rcParams.get('backend.qt4', None)
7676
if mpqt is None:
@@ -92,27 +92,28 @@ def get_options():
9292
"""Return a list of acceptable QT APIs, in decreasing order of
9393
preference
9494
"""
95-
#already imported Qt somewhere. Use that
95+
# already imported Qt somewhere. Use that
9696
loaded = loaded_api()
9797
if loaded is not None:
9898
return [loaded]
9999

100100
mpl = sys.modules.get('matplotlib', None)
101101

102102
if mpl is not None and not check_version(mpl.__version__, '1.0.2'):
103-
#1.0.1 only supports PyQt4 v1
103+
# 1.0.1 only supports PyQt4 v1
104104
return [QT_API_PYQT_DEFAULT]
105105

106106
if os.environ.get('QT_API', None) is None:
107-
#no ETS variable. Ask mpl, then use either
108-
return matplotlib_options(mpl) or [QT_API_PYQT_DEFAULT, QT_API_PYSIDE, QT_API_PYQT5]
107+
# no ETS variable. Ask mpl, then use either
108+
return matplotlib_options(mpl) or [QT_API_PYQT_DEFAULT, QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT5]
109109

110-
#ETS variable present. Will fallback to external.qt
110+
# ETS variable present. Will fallback to external.qt
111111
return None
112112

113+
113114
api_opts = get_options()
114115
if api_opts is not None:
115116
QtCore, QtGui, QtSvg, QT_API = load_qt(api_opts)
116117

117-
else: # use ETS variable
118+
else: # use ETS variable
118119
from pydev_ipython.qt import QtCore, QtGui, QtSvg, QT_API

src/debugpy/_vendored/pydevd/pydev_ipython/qt_loaders.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
# Available APIs.
1717
QT_API_PYQT = 'pyqt'
1818
QT_API_PYQTv1 = 'pyqtv1'
19-
QT_API_PYQT_DEFAULT = 'pyqtdefault' # don't set SIP explicitly
19+
QT_API_PYQT_DEFAULT = 'pyqtdefault' # don't set SIP explicitly
2020
QT_API_PYSIDE = 'pyside'
21+
QT_API_PYSIDE2 = 'pyside2'
2122
QT_API_PYQT5 = 'pyqt5'
2223

2324

@@ -45,6 +46,7 @@ def load_module(self, fullname):
4546
already imported an Incompatible QT Binding: %s
4647
""" % (fullname, loaded_api()))
4748

49+
4850
ID = ImportDenier()
4951
sys.meta_path.append(ID)
5052

@@ -58,6 +60,7 @@ def commit_api(api):
5860
ID.forbid('PyQt5')
5961
else:
6062
ID.forbid('PySide')
63+
ID.forbid('PySide2')
6164

6265

6366
def loaded_api():
@@ -68,7 +71,7 @@ def loaded_api():
6871
6972
Returns
7073
-------
71-
None, 'pyside', 'pyqt', or 'pyqtv1'
74+
None, 'pyside', 'pyside2', 'pyqt', or 'pyqtv1'
7275
"""
7376
if 'PyQt4.QtCore' in sys.modules:
7477
if qtapi_version() == 2:
@@ -77,6 +80,8 @@ def loaded_api():
7780
return QT_API_PYQTv1
7881
elif 'PySide.QtCore' in sys.modules:
7982
return QT_API_PYSIDE
83+
elif 'PySide2.QtCore' in sys.modules:
84+
return QT_API_PYSIDE2
8085
elif 'PyQt5.QtCore' in sys.modules:
8186
return QT_API_PYQT5
8287
return None
@@ -99,6 +104,7 @@ def has_binding(api):
99104
# this will cause a crash in sip (#1431)
100105
# check for complete presence before importing
101106
module_name = {QT_API_PYSIDE: 'PySide',
107+
QT_API_PYSIDE2: 'PySide2',
102108
QT_API_PYQT: 'PyQt4',
103109
QT_API_PYQTv1: 'PyQt4',
104110
QT_API_PYQT_DEFAULT: 'PyQt4',
@@ -108,14 +114,14 @@ def has_binding(api):
108114

109115
import imp
110116
try:
111-
#importing top level PyQt4/PySide module is ok...
117+
# importing top level PyQt4/PySide module is ok...
112118
mod = __import__(module_name)
113-
#...importing submodules is not
119+
# ...importing submodules is not
114120
imp.find_module('QtCore', mod.__path__)
115121
imp.find_module('QtGui', mod.__path__)
116122
imp.find_module('QtSvg', mod.__path__)
117123

118-
#we can also safely check PySide version
124+
# we can also safely check PySide version
119125
if api == QT_API_PYSIDE:
120126
return check_version(mod.__version__, '1.0.3')
121127
else:
@@ -189,6 +195,7 @@ def import_pyqt4(version=2):
189195
api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT
190196
return QtCore, QtGui, QtSvg, api
191197

198+
192199
def import_pyqt5():
193200
"""
194201
Import PyQt5
@@ -214,6 +221,16 @@ def import_pyside():
214221
return QtCore, QtGui, QtSvg, QT_API_PYSIDE
215222

216223

224+
def import_pyside2():
225+
"""
226+
Import PySide2
227+
228+
ImportErrors raised within this function are non-recoverable
229+
"""
230+
from PySide2 import QtGui, QtCore, QtSvg # @UnresolvedImport
231+
return QtCore, QtGui, QtSvg, QT_API_PYSIDE
232+
233+
217234
def load_qt(api_options):
218235
"""
219236
Attempt to import Qt, given a preference list
@@ -241,6 +258,7 @@ def load_qt(api_options):
241258
an incompatible library has already been installed)
242259
"""
243260
loaders = {QT_API_PYSIDE: import_pyside,
261+
QT_API_PYSIDE2: import_pyside2,
244262
QT_API_PYQT: import_pyqt4,
245263
QT_API_PYQTv1: partial(import_pyqt4, version=1),
246264
QT_API_PYQT_DEFAULT: partial(import_pyqt4, version=None),
@@ -251,14 +269,14 @@ def load_qt(api_options):
251269

252270
if api not in loaders:
253271
raise RuntimeError(
254-
"Invalid Qt API %r, valid values are: %r, %r, %r, %r, %r" %
255-
(api, QT_API_PYSIDE, QT_API_PYQT,
272+
"Invalid Qt API %r, valid values are: %r, %r, %r, %r, %r, %r" %
273+
(api, QT_API_PYSIDE, QT_API_PYSIDE, QT_API_PYQT,
256274
QT_API_PYQTv1, QT_API_PYQT_DEFAULT, QT_API_PYQT5))
257275

258276
if not can_import(api):
259277
continue
260278

261-
#cannot safely recover from an ImportError during this
279+
# cannot safely recover from an ImportError during this
262280
result = loaders[api]()
263281
api = result[-1] # changed if api = QT_API_PYQT_DEFAULT
264282
commit_api(api)
@@ -273,9 +291,11 @@ def load_qt(api_options):
273291
PyQt4 installed: %s
274292
PyQt5 installed: %s
275293
PySide >= 1.0.3 installed: %s
294+
PySide2 installed: %s
276295
Tried to load: %r
277296
""" % (loaded_api(),
278297
has_binding(QT_API_PYQT),
279298
has_binding(QT_API_PYQT5),
280299
has_binding(QT_API_PYSIDE),
300+
has_binding(QT_API_PYSIDE2),
281301
api_options))

src/debugpy/_vendored/pydevd/pydevd.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@
6464
from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory
6565
from _pydevd_bundle.pydevd_trace_dispatch import (
6666
trace_dispatch as _trace_dispatch, global_cache_skips, global_cache_frame_skips, fix_top_level_trace_and_get_trace_func)
67-
from _pydevd_bundle.pydevd_utils import save_main_module, is_current_thread_main_thread
67+
from _pydevd_bundle.pydevd_utils import save_main_module, is_current_thread_main_thread, \
68+
import_attr_from_module
6869
from _pydevd_frame_eval.pydevd_frame_eval_main import (
6970
frame_eval_func, dummy_trace_dispatch)
7071
import pydev_ipython # @UnusedImport
@@ -1536,34 +1537,38 @@ def init_gui_support(self):
15361537
if self._installed_gui_support:
15371538
return
15381539
self._installed_gui_support = True
1539-
# prepare debugger for integration with GUI event loop
1540-
from pydev_ipython.matplotlibtools import activate_matplotlib, activate_pylab, activate_pyplot, do_enable_gui
1541-
from pydev_ipython.inputhook import enable_gui
15421540

1543-
# enalbe_gui and enable_gui_function in activate_matplotlib should be called in main thread. Unlike integrated console,
1541+
# enable_gui and enable_gui_function in activate_matplotlib should be called in main thread. Unlike integrated console,
15441542
# in the debug console we have no interpreter instance with exec_queue, but we run this code in the main
15451543
# thread and can call it directly.
1546-
class _MatplotlibHelper:
1544+
class _ReturnGuiLoopControlHelper:
15471545
_return_control_osc = False
15481546

15491547
def return_control():
15501548
# Some of the input hooks (e.g. Qt4Agg) check return control without doing
15511549
# a single operation, so we don't return True on every
15521550
# call when the debug hook is in place to allow the GUI to run
1553-
_MatplotlibHelper._return_control_osc = not _MatplotlibHelper._return_control_osc
1554-
return _MatplotlibHelper._return_control_osc
1551+
_ReturnGuiLoopControlHelper._return_control_osc = not _ReturnGuiLoopControlHelper._return_control_osc
1552+
return _ReturnGuiLoopControlHelper._return_control_osc
1553+
1554+
from pydev_ipython.inputhook import set_return_control_callback, enable_gui
15551555

1556-
from pydev_ipython.inputhook import set_return_control_callback
15571556
set_return_control_callback(return_control)
15581557

15591558
if self._gui_event_loop == 'matplotlib':
1559+
# prepare debugger for matplotlib integration with GUI event loop
1560+
from pydev_ipython.matplotlibtools import activate_matplotlib, activate_pylab, activate_pyplot, do_enable_gui
1561+
15601562
self.mpl_modules_for_patching = {"matplotlib": lambda: activate_matplotlib(do_enable_gui),
15611563
"matplotlib.pyplot": activate_pyplot,
15621564
"pylab": activate_pylab }
15631565
else:
15641566
self.activate_gui_function = enable_gui
15651567

15661568
def _activate_gui_if_needed(self):
1569+
if self.gui_in_use:
1570+
return
1571+
15671572
if len(self.mpl_modules_for_patching) > 0:
15681573
if is_current_thread_main_thread(): # Note that we call only in the main thread.
15691574
for module in dict_keys(self.mpl_modules_for_patching):
@@ -1574,20 +1579,17 @@ def _activate_gui_if_needed(self):
15741579
self.gui_in_use = True
15751580

15761581
if self.activate_gui_function:
1577-
if is_current_thread_main_thread(): # Only call enable_gui in the main thread.
1582+
if is_current_thread_main_thread(): # Only call enable_gui in the main thread.
15781583
try:
15791584
# First try to activate builtin GUI event loops.
15801585
self.activate_gui_function(self._gui_event_loop)
15811586
self.activate_gui_function = None
15821587
self.gui_in_use = True
15831588
except ValueError:
15841589
# The user requested a custom GUI event loop, try to import it.
1585-
from importlib import import_module
15861590
from pydev_ipython.inputhook import set_inputhook
15871591
try:
1588-
module_name, inputhook_name = self._gui_event_loop.rsplit('.', 1)
1589-
module = import_module(module_name)
1590-
inputhook_function = getattr(module, inputhook_name)
1592+
inputhook_function = import_attr_from_module(self._gui_event_loop)
15911593
set_inputhook(inputhook_function)
15921594
self.gui_in_use = True
15931595
except Exception as e:
@@ -1736,7 +1738,7 @@ def process_internal_commands(self):
17361738
while True:
17371739
int_cmd = queue.get(False)
17381740

1739-
if not self.mpl_hooks_in_debug_console and isinstance(int_cmd, InternalConsoleExec):
1741+
if not self.mpl_hooks_in_debug_console and isinstance(int_cmd, InternalConsoleExec) and not self.gui_in_use:
17401742
# add import hooks for matplotlib patches if only debug console was started
17411743
try:
17421744
self.init_matplotlib_in_debug_console()

0 commit comments

Comments
 (0)