Skip to content

Commit 71d42ed

Browse files
committed
Support top-level async. Fixes #951
1 parent 6e247fb commit 71d42ed

File tree

9 files changed

+514
-144
lines changed

9 files changed

+514
-144
lines changed

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevconsole_code_for_ironpython.py renamed to src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevconsole_code.py

Lines changed: 117 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
"""Utilities needed to emulate Python's interactive interpreter.
1+
"""
2+
A copy of the code module in the standard library with some changes to work with
3+
async evaluation.
24
5+
Utilities needed to emulate Python's interactive interpreter.
36
"""
47

58
# Inspired by similar code by Jeff Epler and Fredrik Lundh.
69

710
import sys
811
import traceback
12+
import inspect
913

1014
# START --------------------------- from codeop import CommandCompiler, compile_command
1115
# START --------------------------- from codeop import CommandCompiler, compile_command
@@ -100,18 +104,21 @@ def _maybe_compile(compiler, source, filename, symbol):
100104

101105
try:
102106
code1 = compiler(source + "\n", filename, symbol)
103-
except SyntaxError as err1:
104-
pass
107+
except SyntaxError as e:
108+
err1 = e
105109

106110
try:
107111
code2 = compiler(source + "\n\n", filename, symbol)
108-
except SyntaxError as err2:
109-
pass
112+
except SyntaxError as e:
113+
err2 = e
110114

111-
if code:
112-
return code
113-
if not code1 and repr(err1) == repr(err2):
114-
raise SyntaxError(err1)
115+
try:
116+
if code:
117+
return code
118+
if not code1 and repr(err1) == repr(err2):
119+
raise err1
120+
finally:
121+
err1 = err2 = None
115122

116123

117124
def _compile(source, filename, symbol):
@@ -148,6 +155,12 @@ class Compile:
148155
def __init__(self):
149156
self.flags = PyCF_DONT_IMPLY_DEDENT
150157

158+
try:
159+
from ast import PyCF_ALLOW_TOP_LEVEL_AWAIT
160+
self.flags |= PyCF_ALLOW_TOP_LEVEL_AWAIT
161+
except:
162+
pass
163+
151164
def __call__(self, source, filename, symbol):
152165
codeob = compile(source, filename, symbol, self.flags, 1)
153166
for feature in _features:
@@ -197,19 +210,33 @@ def __call__(self, source, filename="<input>", symbol="single"):
197210
__all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact",
198211
"compile_command"]
199212

213+
from _pydev_bundle._pydev_saved_modules import threading
200214

201-
def softspace(file, newvalue):
202-
oldvalue = 0
203-
try:
204-
oldvalue = file.softspace
205-
except AttributeError:
206-
pass
207-
try:
208-
file.softspace = newvalue
209-
except (AttributeError, TypeError):
210-
# "attribute-less object" or "read-only attributes"
211-
pass
212-
return oldvalue
215+
216+
class _EvalAwaitInNewEventLoop(threading.Thread):
217+
218+
def __init__(self, compiled, updated_globals, updated_locals):
219+
threading.Thread.__init__(self)
220+
self.daemon = True
221+
self._compiled = compiled
222+
self._updated_globals = updated_globals
223+
self._updated_locals = updated_locals
224+
225+
# Output
226+
self.evaluated_value = None
227+
self.exc = None
228+
229+
async def _async_func(self):
230+
return await eval(self._compiled, self._updated_locals, self._updated_globals)
231+
232+
def run(self):
233+
try:
234+
import asyncio
235+
loop = asyncio.new_event_loop()
236+
asyncio.set_event_loop(loop)
237+
self.evaluated_value = asyncio.run(self._async_func())
238+
except:
239+
self.exc = sys.exc_info()
213240

214241

215242
class InteractiveInterpreter:
@@ -240,7 +267,7 @@ def runsource(self, source, filename="<input>", symbol="single"):
240267
241268
Arguments are as for compile_command().
242269
243-
One several things can happen:
270+
One of several things can happen:
244271
245272
1) The input is incorrect; compile_command() raised an
246273
exception (SyntaxError or OverflowError). A syntax traceback
@@ -287,14 +314,24 @@ def runcode(self, code):
287314
288315
"""
289316
try:
290-
exec(code, self.locals)
317+
is_async = False
318+
if hasattr(inspect, 'CO_COROUTINE'):
319+
is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
320+
321+
if is_async:
322+
t = _EvalAwaitInNewEventLoop(code, self.locals, None)
323+
t.start()
324+
t.join()
325+
326+
if t.exc:
327+
raise t.exc[1].with_traceback(t.exc[2])
328+
329+
else:
330+
exec(code, self.locals)
291331
except SystemExit:
292332
raise
293333
except:
294334
self.showtraceback()
295-
else:
296-
if softspace(sys.stdout, 0):
297-
sys.stdout.write('\n')
298335

299336
def showsyntaxerror(self, filename=None):
300337
"""Display the syntax error that just occurred.
@@ -308,45 +345,49 @@ def showsyntaxerror(self, filename=None):
308345
The output is written by self.write(), below.
309346
310347
"""
311-
type, value, sys.last_traceback = sys.exc_info()
348+
type, value, tb = sys.exc_info()
312349
sys.last_type = type
313350
sys.last_value = value
351+
sys.last_traceback = tb
314352
if filename and type is SyntaxError:
315353
# Work hard to stuff the correct filename in the exception
316354
try:
317-
msg, (dummy_filename, lineno, offset, line) = value
318-
except:
355+
msg, (dummy_filename, lineno, offset, line) = value.args
356+
except ValueError:
319357
# Not the format we expect; leave it alone
320358
pass
321359
else:
322360
# Stuff in the right filename
323361
value = SyntaxError(msg, (filename, lineno, offset, line))
324362
sys.last_value = value
325-
list = traceback.format_exception_only(type, value)
326-
map(self.write, list)
363+
if sys.excepthook is sys.__excepthook__:
364+
lines = traceback.format_exception_only(type, value)
365+
self.write(''.join(lines))
366+
else:
367+
# If someone has set sys.excepthook, we let that take precedence
368+
# over self.write
369+
sys.excepthook(type, value, tb)
327370

328-
def showtraceback(self, *args, **kwargs):
371+
def showtraceback(self):
329372
"""Display the exception that just occurred.
330373
331374
We remove the first stack item because it is our own code.
332375
333376
The output is written by self.write(), below.
334377
335378
"""
379+
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
380+
sys.last_traceback = last_tb
336381
try:
337-
type, value, tb = sys.exc_info()
338-
sys.last_type = type
339-
sys.last_value = value
340-
sys.last_traceback = tb
341-
tblist = traceback.extract_tb(tb)
342-
del tblist[:1]
343-
list = traceback.format_list(tblist)
344-
if list:
345-
list.insert(0, "Traceback (most recent call last):\n")
346-
list[len(list):] = traceback.format_exception_only(type, value)
382+
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
383+
if sys.excepthook is sys.__excepthook__:
384+
self.write(''.join(lines))
385+
else:
386+
# If someone has set sys.excepthook, we let that take precedence
387+
# over self.write
388+
sys.excepthook(ei[0], ei[1], last_tb)
347389
finally:
348-
tblist = tb = None
349-
map(self.write, list)
390+
last_tb = ei = None
350391

351392
def write(self, data):
352393
"""Write a string.
@@ -384,45 +425,46 @@ def resetbuffer(self):
384425
"""Reset the input buffer."""
385426
self.buffer = []
386427

387-
def interact(self, banner=None):
428+
def interact(self, banner=None, exitmsg=None):
388429
"""Closely emulate the interactive Python console.
389430
390-
The optional banner argument specify the banner to print
431+
The optional banner argument specifies the banner to print
391432
before the first interaction; by default it prints a banner
392433
similar to the one printed by the real Python interpreter,
393434
followed by the current class name in parentheses (so as not
394435
to confuse this with the real interpreter -- since it's so
395436
close!).
396437
438+
The optional exitmsg argument specifies the exit message
439+
printed when exiting. Pass the empty string to suppress
440+
printing an exit message. If exitmsg is not given or None,
441+
a default message is printed.
442+
397443
"""
398444
try:
399-
sys.ps1 # @UndefinedVariable
445+
sys.ps1
400446
except AttributeError:
401447
sys.ps1 = ">>> "
402448
try:
403-
sys.ps2 # @UndefinedVariable
449+
sys.ps2
404450
except AttributeError:
405451
sys.ps2 = "... "
406452
cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
407453
if banner is None:
408454
self.write("Python %s on %s\n%s\n(%s)\n" %
409455
(sys.version, sys.platform, cprt,
410456
self.__class__.__name__))
411-
else:
457+
elif banner:
412458
self.write("%s\n" % str(banner))
413459
more = 0
414460
while 1:
415461
try:
416462
if more:
417-
prompt = sys.ps2 # @UndefinedVariable
463+
prompt = sys.ps2
418464
else:
419-
prompt = sys.ps1 # @UndefinedVariable
465+
prompt = sys.ps1
420466
try:
421467
line = self.raw_input(prompt)
422-
# Can be None if sys.stdin was redefined
423-
encoding = getattr(sys.stdin, "encoding", None)
424-
if encoding and not isinstance(line, str):
425-
line = line.decode(encoding)
426468
except EOFError:
427469
self.write("\n")
428470
break
@@ -432,6 +474,10 @@ def interact(self, banner=None):
432474
self.write("\nKeyboardInterrupt\n")
433475
self.resetbuffer()
434476
more = 0
477+
if exitmsg is None:
478+
self.write('now exiting %s...\n' % self.__class__.__name__)
479+
elif exitmsg != '':
480+
self.write('%s\n' % exitmsg)
435481

436482
def push(self, line):
437483
"""Push a line to the interpreter.
@@ -461,14 +507,14 @@ def raw_input(self, prompt=""):
461507
When the user enters the EOF key sequence, EOFError is raised.
462508
463509
The base implementation uses the built-in function
464-
raw_input(); a subclass may replace this with a different
510+
input(); a subclass may replace this with a different
465511
implementation.
466512
467513
"""
468514
return input(prompt)
469515

470516

471-
def interact(banner=None, readfunc=None, local=None):
517+
def interact(banner=None, readfunc=None, local=None, exitmsg=None):
472518
"""Closely emulate the interactive Python interpreter.
473519
474520
This is a backwards compatible interface to the InteractiveConsole
@@ -480,6 +526,7 @@ def interact(banner=None, readfunc=None, local=None):
480526
banner -- passed to InteractiveConsole.interact()
481527
readfunc -- if not None, replaces InteractiveConsole.raw_input()
482528
local -- passed to InteractiveInterpreter.__init__()
529+
exitmsg -- passed to InteractiveConsole.interact()
483530
484531
"""
485532
console = InteractiveConsole(local)
@@ -490,9 +537,18 @@ def interact(banner=None, readfunc=None, local=None):
490537
import readline
491538
except ImportError:
492539
pass
493-
console.interact(banner)
540+
console.interact(banner, exitmsg)
541+
494542

543+
if __name__ == "__main__":
544+
import argparse
495545

496-
if __name__ == '__main__':
497-
import pdb
498-
pdb.run("interact()\n")
546+
parser = argparse.ArgumentParser()
547+
parser.add_argument('-q', action='store_true',
548+
help="don't print version and copyright messages")
549+
args = parser.parse_args()
550+
if args.q or sys.flags.quiet:
551+
banner = ''
552+
else:
553+
banner = None
554+
interact(banner)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ def __init__(self, seq, roffset, coffset, rows, cols, format, thread_id, frame_i
833833
def do_it(self, dbg):
834834
try:
835835
frame = dbg.find_frame(self.thread_id, self.frame_id)
836-
var = pydevd_vars.eval_in_context(self.name, frame.f_globals, frame.f_locals)
836+
var = pydevd_vars.eval_in_context(self.name, frame.f_globals, frame.f_locals, py_db=dbg)
837837
xml = pydevd_vars.table_like_struct_to_xml(var, self.name, self.roffset, self.coffset, self.rows, self.cols, self.format)
838838
cmd = dbg.cmd_factory.make_get_array_message(self.sequence, xml)
839839
dbg.writer.add_command(cmd)

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

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
'''
33
import sys
44
import traceback
5-
from code import InteractiveConsole
6-
5+
from _pydevd_bundle.pydevconsole_code import InteractiveConsole, _EvalAwaitInNewEventLoop
76
from _pydev_bundle import _pydev_completer
87
from _pydev_bundle.pydev_console_utils import BaseInterpreterInterface, BaseStdIn
98
from _pydev_bundle.pydev_imports import Exec
@@ -12,6 +11,8 @@
1211
from _pydevd_bundle.pydevd_io import IOBuf
1312
from pydevd_tracing import get_exception_traceback_str
1413
from _pydevd_bundle.pydevd_xml import make_valid_xml_value
14+
import inspect
15+
from _pydevd_bundle.pydevd_save_locals import update_globals_and_locals
1516

1617
CONSOLE_OUTPUT = "output"
1718
CONSOLE_ERROR = "error"
@@ -152,8 +153,29 @@ def runcode(self, code):
152153
153154
"""
154155
try:
155-
Exec(code, self.frame.f_globals, self.frame.f_locals)
156-
pydevd_save_locals.save_locals(self.frame)
156+
updated_globals = self.get_namespace()
157+
initial_globals = updated_globals.copy()
158+
159+
updated_locals = None
160+
161+
is_async = False
162+
if hasattr(inspect, 'CO_COROUTINE'):
163+
is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
164+
165+
if is_async:
166+
t = _EvalAwaitInNewEventLoop(code, updated_globals, updated_locals)
167+
t.start()
168+
t.join()
169+
170+
update_globals_and_locals(updated_globals, initial_globals, self.frame)
171+
if t.exc:
172+
raise t.exc[1].with_traceback(t.exc[2])
173+
174+
else:
175+
try:
176+
exec(code, updated_globals, updated_locals)
177+
finally:
178+
update_globals_and_locals(updated_globals, initial_globals, self.frame)
157179
except SystemExit:
158180
raise
159181
except:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
'pydev_umd.py': PYDEV_FILE,
7171
'pydev_versioncheck.py': PYDEV_FILE,
7272
'pydevconsole.py': PYDEV_FILE,
73-
'pydevconsole_code_for_ironpython.py': PYDEV_FILE,
73+
'pydevconsole_code.py': PYDEV_FILE,
7474
'pydevd.py': PYDEV_FILE,
7575
'pydevd_additional_thread_info.py': PYDEV_FILE,
7676
'pydevd_additional_thread_info_regular.py': PYDEV_FILE,

0 commit comments

Comments
 (0)