diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevconsole_code_for_ironpython.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevconsole_code.py
similarity index 78%
rename from src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevconsole_code_for_ironpython.py
rename to src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevconsole_code.py
index 229f9f156..e6ba30023 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevconsole_code_for_ironpython.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevconsole_code.py
@@ -1,11 +1,15 @@
-"""Utilities needed to emulate Python's interactive interpreter.
+"""
+A copy of the code module in the standard library with some changes to work with
+async evaluation.
+Utilities needed to emulate Python's interactive interpreter.
"""
# Inspired by similar code by Jeff Epler and Fredrik Lundh.
import sys
import traceback
+import inspect
# START --------------------------- from codeop import CommandCompiler, compile_command
# START --------------------------- from codeop import CommandCompiler, compile_command
@@ -100,18 +104,21 @@ def _maybe_compile(compiler, source, filename, symbol):
try:
code1 = compiler(source + "\n", filename, symbol)
- except SyntaxError as err1:
- pass
+ except SyntaxError as e:
+ err1 = e
try:
code2 = compiler(source + "\n\n", filename, symbol)
- except SyntaxError as err2:
- pass
+ except SyntaxError as e:
+ err2 = e
- if code:
- return code
- if not code1 and repr(err1) == repr(err2):
- raise SyntaxError(err1)
+ try:
+ if code:
+ return code
+ if not code1 and repr(err1) == repr(err2):
+ raise err1
+ finally:
+ err1 = err2 = None
def _compile(source, filename, symbol):
@@ -148,6 +155,12 @@ class Compile:
def __init__(self):
self.flags = PyCF_DONT_IMPLY_DEDENT
+ try:
+ from ast import PyCF_ALLOW_TOP_LEVEL_AWAIT
+ self.flags |= PyCF_ALLOW_TOP_LEVEL_AWAIT
+ except:
+ pass
+
def __call__(self, source, filename, symbol):
codeob = compile(source, filename, symbol, self.flags, 1)
for feature in _features:
@@ -197,19 +210,33 @@ def __call__(self, source, filename="", symbol="single"):
__all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact",
"compile_command"]
+from _pydev_bundle._pydev_saved_modules import threading
-def softspace(file, newvalue):
- oldvalue = 0
- try:
- oldvalue = file.softspace
- except AttributeError:
- pass
- try:
- file.softspace = newvalue
- except (AttributeError, TypeError):
- # "attribute-less object" or "read-only attributes"
- pass
- return oldvalue
+
+class _EvalAwaitInNewEventLoop(threading.Thread):
+
+ def __init__(self, compiled, updated_globals, updated_locals):
+ threading.Thread.__init__(self)
+ self.daemon = True
+ self._compiled = compiled
+ self._updated_globals = updated_globals
+ self._updated_locals = updated_locals
+
+ # Output
+ self.evaluated_value = None
+ self.exc = None
+
+ async def _async_func(self):
+ return await eval(self._compiled, self._updated_locals, self._updated_globals)
+
+ def run(self):
+ try:
+ import asyncio
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+ self.evaluated_value = asyncio.run(self._async_func())
+ except:
+ self.exc = sys.exc_info()
class InteractiveInterpreter:
@@ -240,7 +267,7 @@ def runsource(self, source, filename="", symbol="single"):
Arguments are as for compile_command().
- One several things can happen:
+ One of several things can happen:
1) The input is incorrect; compile_command() raised an
exception (SyntaxError or OverflowError). A syntax traceback
@@ -287,14 +314,24 @@ def runcode(self, code):
"""
try:
- exec(code, self.locals)
+ is_async = False
+ if hasattr(inspect, 'CO_COROUTINE'):
+ is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
+
+ if is_async:
+ t = _EvalAwaitInNewEventLoop(code, self.locals, None)
+ t.start()
+ t.join()
+
+ if t.exc:
+ raise t.exc[1].with_traceback(t.exc[2])
+
+ else:
+ exec(code, self.locals)
except SystemExit:
raise
except:
self.showtraceback()
- else:
- if softspace(sys.stdout, 0):
- sys.stdout.write('\n')
def showsyntaxerror(self, filename=None):
"""Display the syntax error that just occurred.
@@ -308,24 +345,30 @@ def showsyntaxerror(self, filename=None):
The output is written by self.write(), below.
"""
- type, value, sys.last_traceback = sys.exc_info()
+ type, value, tb = sys.exc_info()
sys.last_type = type
sys.last_value = value
+ sys.last_traceback = tb
if filename and type is SyntaxError:
# Work hard to stuff the correct filename in the exception
try:
- msg, (dummy_filename, lineno, offset, line) = value
- except:
+ msg, (dummy_filename, lineno, offset, line) = value.args
+ except ValueError:
# Not the format we expect; leave it alone
pass
else:
# Stuff in the right filename
value = SyntaxError(msg, (filename, lineno, offset, line))
sys.last_value = value
- list = traceback.format_exception_only(type, value)
- map(self.write, list)
+ if sys.excepthook is sys.__excepthook__:
+ lines = traceback.format_exception_only(type, value)
+ self.write(''.join(lines))
+ else:
+ # If someone has set sys.excepthook, we let that take precedence
+ # over self.write
+ sys.excepthook(type, value, tb)
- def showtraceback(self, *args, **kwargs):
+ def showtraceback(self):
"""Display the exception that just occurred.
We remove the first stack item because it is our own code.
@@ -333,20 +376,18 @@ def showtraceback(self, *args, **kwargs):
The output is written by self.write(), below.
"""
+ sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
+ sys.last_traceback = last_tb
try:
- type, value, tb = sys.exc_info()
- sys.last_type = type
- sys.last_value = value
- sys.last_traceback = tb
- tblist = traceback.extract_tb(tb)
- del tblist[:1]
- list = traceback.format_list(tblist)
- if list:
- list.insert(0, "Traceback (most recent call last):\n")
- list[len(list):] = traceback.format_exception_only(type, value)
+ lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
+ if sys.excepthook is sys.__excepthook__:
+ self.write(''.join(lines))
+ else:
+ # If someone has set sys.excepthook, we let that take precedence
+ # over self.write
+ sys.excepthook(ei[0], ei[1], last_tb)
finally:
- tblist = tb = None
- map(self.write, list)
+ last_tb = ei = None
def write(self, data):
"""Write a string.
@@ -384,23 +425,28 @@ def resetbuffer(self):
"""Reset the input buffer."""
self.buffer = []
- def interact(self, banner=None):
+ def interact(self, banner=None, exitmsg=None):
"""Closely emulate the interactive Python console.
- The optional banner argument specify the banner to print
+ The optional banner argument specifies the banner to print
before the first interaction; by default it prints a banner
similar to the one printed by the real Python interpreter,
followed by the current class name in parentheses (so as not
to confuse this with the real interpreter -- since it's so
close!).
+ The optional exitmsg argument specifies the exit message
+ printed when exiting. Pass the empty string to suppress
+ printing an exit message. If exitmsg is not given or None,
+ a default message is printed.
+
"""
try:
- sys.ps1 # @UndefinedVariable
+ sys.ps1
except AttributeError:
sys.ps1 = ">>> "
try:
- sys.ps2 # @UndefinedVariable
+ sys.ps2
except AttributeError:
sys.ps2 = "... "
cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
@@ -408,21 +454,17 @@ def interact(self, banner=None):
self.write("Python %s on %s\n%s\n(%s)\n" %
(sys.version, sys.platform, cprt,
self.__class__.__name__))
- else:
+ elif banner:
self.write("%s\n" % str(banner))
more = 0
while 1:
try:
if more:
- prompt = sys.ps2 # @UndefinedVariable
+ prompt = sys.ps2
else:
- prompt = sys.ps1 # @UndefinedVariable
+ prompt = sys.ps1
try:
line = self.raw_input(prompt)
- # Can be None if sys.stdin was redefined
- encoding = getattr(sys.stdin, "encoding", None)
- if encoding and not isinstance(line, str):
- line = line.decode(encoding)
except EOFError:
self.write("\n")
break
@@ -432,6 +474,10 @@ def interact(self, banner=None):
self.write("\nKeyboardInterrupt\n")
self.resetbuffer()
more = 0
+ if exitmsg is None:
+ self.write('now exiting %s...\n' % self.__class__.__name__)
+ elif exitmsg != '':
+ self.write('%s\n' % exitmsg)
def push(self, line):
"""Push a line to the interpreter.
@@ -461,14 +507,14 @@ def raw_input(self, prompt=""):
When the user enters the EOF key sequence, EOFError is raised.
The base implementation uses the built-in function
- raw_input(); a subclass may replace this with a different
+ input(); a subclass may replace this with a different
implementation.
"""
return input(prompt)
-def interact(banner=None, readfunc=None, local=None):
+def interact(banner=None, readfunc=None, local=None, exitmsg=None):
"""Closely emulate the interactive Python interpreter.
This is a backwards compatible interface to the InteractiveConsole
@@ -480,6 +526,7 @@ def interact(banner=None, readfunc=None, local=None):
banner -- passed to InteractiveConsole.interact()
readfunc -- if not None, replaces InteractiveConsole.raw_input()
local -- passed to InteractiveInterpreter.__init__()
+ exitmsg -- passed to InteractiveConsole.interact()
"""
console = InteractiveConsole(local)
@@ -490,9 +537,18 @@ def interact(banner=None, readfunc=None, local=None):
import readline
except ImportError:
pass
- console.interact(banner)
+ console.interact(banner, exitmsg)
+
+if __name__ == "__main__":
+ import argparse
-if __name__ == '__main__':
- import pdb
- pdb.run("interact()\n")
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-q', action='store_true',
+ help="don't print version and copyright messages")
+ args = parser.parse_args()
+ if args.q or sys.flags.quiet:
+ banner = ''
+ else:
+ banner = None
+ interact(banner)
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py
index 4c254c513..930adda8f 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py
@@ -833,7 +833,7 @@ def __init__(self, seq, roffset, coffset, rows, cols, format, thread_id, frame_i
def do_it(self, dbg):
try:
frame = dbg.find_frame(self.thread_id, self.frame_id)
- var = pydevd_vars.eval_in_context(self.name, frame.f_globals, frame.f_locals)
+ var = pydevd_vars.eval_in_context(self.name, frame.f_globals, frame.f_locals, py_db=dbg)
xml = pydevd_vars.table_like_struct_to_xml(var, self.name, self.roffset, self.coffset, self.rows, self.cols, self.format)
cmd = dbg.cmd_factory.make_get_array_message(self.sequence, xml)
dbg.writer.add_command(cmd)
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_console.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_console.py
index cf151b75e..925e010a5 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_console.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_console.py
@@ -2,8 +2,7 @@
'''
import sys
import traceback
-from code import InteractiveConsole
-
+from _pydevd_bundle.pydevconsole_code import InteractiveConsole, _EvalAwaitInNewEventLoop
from _pydev_bundle import _pydev_completer
from _pydev_bundle.pydev_console_utils import BaseInterpreterInterface, BaseStdIn
from _pydev_bundle.pydev_imports import Exec
@@ -12,6 +11,8 @@
from _pydevd_bundle.pydevd_io import IOBuf
from pydevd_tracing import get_exception_traceback_str
from _pydevd_bundle.pydevd_xml import make_valid_xml_value
+import inspect
+from _pydevd_bundle.pydevd_save_locals import update_globals_and_locals
CONSOLE_OUTPUT = "output"
CONSOLE_ERROR = "error"
@@ -152,8 +153,29 @@ def runcode(self, code):
"""
try:
- Exec(code, self.frame.f_globals, self.frame.f_locals)
- pydevd_save_locals.save_locals(self.frame)
+ updated_globals = self.get_namespace()
+ initial_globals = updated_globals.copy()
+
+ updated_locals = None
+
+ is_async = False
+ if hasattr(inspect, 'CO_COROUTINE'):
+ is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
+
+ if is_async:
+ t = _EvalAwaitInNewEventLoop(code, updated_globals, updated_locals)
+ t.start()
+ t.join()
+
+ update_globals_and_locals(updated_globals, initial_globals, self.frame)
+ if t.exc:
+ raise t.exc[1].with_traceback(t.exc[2])
+
+ else:
+ try:
+ exec(code, updated_globals, updated_locals)
+ finally:
+ update_globals_and_locals(updated_globals, initial_globals, self.frame)
except SystemExit:
raise
except:
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_dont_trace_files.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_dont_trace_files.py
index d73765399..d37b1fc53 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_dont_trace_files.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_dont_trace_files.py
@@ -70,7 +70,7 @@
'pydev_umd.py': PYDEV_FILE,
'pydev_versioncheck.py': PYDEV_FILE,
'pydevconsole.py': PYDEV_FILE,
- 'pydevconsole_code_for_ironpython.py': PYDEV_FILE,
+ 'pydevconsole_code.py': PYDEV_FILE,
'pydevd.py': PYDEV_FILE,
'pydevd_additional_thread_info.py': PYDEV_FILE,
'pydevd_additional_thread_info_regular.py': PYDEV_FILE,
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_save_locals.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_save_locals.py
index 3c6b0d609..fa1a12520 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_save_locals.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_save_locals.py
@@ -47,6 +47,7 @@ def make_save_locals_impl():
pass
else:
if '__pypy__' in sys.builtin_module_names:
+
def save_locals_pypy_impl(frame):
save_locals(frame)
@@ -58,6 +59,7 @@ def save_locals_pypy_impl(frame):
except:
pass
else:
+
def save_locals_ctypes_impl(frame):
locals_to_fast(ctypes.py_object(frame), ctypes.c_int(0))
@@ -67,3 +69,28 @@ def save_locals_ctypes_impl(frame):
save_locals_impl = make_save_locals_impl()
+
+
+def update_globals_and_locals(updated_globals, initial_globals, frame):
+ # We don't have the locals and passed all in globals, so, we have to
+ # manually choose how to update the variables.
+ #
+ # Note that the current implementation is a bit tricky: it does work in general
+ # but if we do something as 'some_var = 10' and 'some_var' is already defined to have
+ # the value '10' in the globals, we won't actually put that value in the locals
+ # (which means that the frame locals won't be updated).
+ # Still, the approach to have a single namespace was chosen because it was the only
+ # one that enabled creating and using variables during the same evaluation.
+ assert updated_globals is not None
+ f_locals = None
+ for key, val in updated_globals.items():
+ if initial_globals.get(key) is not val:
+ if f_locals is None:
+ # Note: we call f_locals only once because each time
+ # we call it the values may be reset.
+ f_locals = frame.f_locals
+
+ f_locals[key] = val
+
+ if f_locals is not None:
+ save_locals(frame)
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py
index 1ed89636b..801009382 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py
@@ -3,7 +3,7 @@
"""
import pickle
from _pydevd_bundle.pydevd_constants import get_frame, get_current_thread_id, \
- iter_chars, silence_warnings_decorator
+ iter_chars, silence_warnings_decorator, get_global_debugger
from _pydevd_bundle.pydevd_xml import ExceptionOnEvaluate, get_type, var_to_xml
from _pydev_bundle import pydev_log
@@ -17,6 +17,10 @@
from _pydevd_bundle import pydevd_save_locals, pydevd_timeout, pydevd_constants
from _pydev_bundle.pydev_imports import Exec, execfile
from _pydevd_bundle.pydevd_utils import to_string
+import inspect
+from _pydevd_bundle.pydevd_daemon_thread import PyDBDaemonThread
+from _pydevd_bundle.pydevd_save_locals import update_globals_and_locals
+from functools import lru_cache
SENTINEL_VALUE = []
@@ -203,6 +207,7 @@ def custom_operation(dbg, thread_id, frame_id, scope, attrs, style, code_or_file
pydev_log.exception()
+@lru_cache(3)
def _expression_to_evaluate(expression):
keepends = True
lines = expression.splitlines(keepends)
@@ -240,13 +245,27 @@ def _expression_to_evaluate(expression):
return expression
-def eval_in_context(expression, globals, locals=None):
+def eval_in_context(expression, global_vars, local_vars, py_db=None):
result = None
try:
- if locals is None:
- result = eval(_expression_to_evaluate(expression), globals)
+ compiled = compile_as_eval(expression)
+ is_async = inspect.CO_COROUTINE & compiled.co_flags == inspect.CO_COROUTINE
+
+ if is_async:
+ if py_db is None:
+ py_db = get_global_debugger()
+ if py_db is None:
+ raise RuntimeError('Cannot evaluate async without py_db.')
+ t = _EvalAwaitInNewEventLoop(py_db, compiled, global_vars, local_vars)
+ t.start()
+ t.join()
+
+ if t.exc:
+ raise t.exc[1].with_traceback(t.exc[2])
+ else:
+ result = t.evaluated_value
else:
- result = eval(_expression_to_evaluate(expression), globals, locals)
+ result = eval(compiled, global_vars, local_vars)
except (Exception, KeyboardInterrupt):
etype, result, tb = sys.exc_info()
result = ExceptionOnEvaluate(result, etype, tb)
@@ -258,9 +277,9 @@ def eval_in_context(expression, globals, locals=None):
split = expression.split('.')
entry = split[0]
- if locals is None:
- locals = globals
- curr = locals[entry] # Note: we want the KeyError if it's not there.
+ if local_vars is None:
+ local_vars = global_vars
+ curr = local_vars[entry] # Note: we want the KeyError if it's not there.
for entry in split[1:]:
if entry.startswith('__') and not hasattr(curr, entry):
entry = '_%s%s' % (curr.__class__.__name__, entry)
@@ -353,60 +372,117 @@ def on_warn_evaluation_timeout():
return new_func
+_ASYNC_COMPILE_FLAGS = None
+try:
+ from ast import PyCF_ALLOW_TOP_LEVEL_AWAIT
+ _ASYNC_COMPILE_FLAGS = PyCF_ALLOW_TOP_LEVEL_AWAIT
+except:
+ pass
+
+
def compile_as_eval(expression):
'''
:param expression:
- The expression to be compiled.
+ The expression to be _compiled.
:return: code object
:raises Exception if the expression cannot be evaluated.
'''
- return compile(_expression_to_evaluate(expression), '', 'eval')
+ expression_to_evaluate = _expression_to_evaluate(expression)
+ if _ASYNC_COMPILE_FLAGS is not None:
+ return compile(expression_to_evaluate, '', 'eval', _ASYNC_COMPILE_FLAGS)
+ else:
+ return compile(expression_to_evaluate, '', 'eval')
-def _update_globals_and_locals(updated_globals, initial_globals, frame):
- # We don't have the locals and passed all in globals, so, we have to
- # manually choose how to update the variables.
- #
- # Note that the current implementation is a bit tricky: it does work in general
- # but if we do something as 'some_var = 10' and 'some_var' is already defined to have
- # the value '10' in the globals, we won't actually put that value in the locals
- # (which means that the frame locals won't be updated).
- # Still, the approach to have a single namespace was chosen because it was the only
- # one that enabled creating and using variables during the same evaluation.
- assert updated_globals is not None
- f_locals = None
- for key, val in updated_globals.items():
- if initial_globals.get(key) is not val:
- if f_locals is None:
- # Note: we call f_locals only once because each time
- # we call it the values may be reset.
- f_locals = frame.f_locals
-
- f_locals[key] = val
-
- if f_locals is not None:
- pydevd_save_locals.save_locals(frame)
+def _compile_as_exec(expression):
+ '''
+
+ :param expression:
+ The expression to be _compiled.
+
+ :return: code object
+
+ :raises Exception if the expression cannot be evaluated.
+ '''
+ expression_to_evaluate = _expression_to_evaluate(expression)
+ if _ASYNC_COMPILE_FLAGS is not None:
+ return compile(expression_to_evaluate, '', 'exec', _ASYNC_COMPILE_FLAGS)
+ else:
+ return compile(expression_to_evaluate, '', 'exec')
+
+
+class _EvalAwaitInNewEventLoop(PyDBDaemonThread):
+
+ def __init__(self, py_db, compiled, updated_globals, updated_locals):
+ PyDBDaemonThread.__init__(self, py_db)
+ self._compiled = compiled
+ self._updated_globals = updated_globals
+ self._updated_locals = updated_locals
+
+ # Output
+ self.evaluated_value = None
+ self.exc = None
+
+ async def _async_func(self):
+ return await eval(self._compiled, self._updated_locals, self._updated_globals)
+
+ def _on_run(self):
+ try:
+ import asyncio
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+ self.evaluated_value = asyncio.run(self._async_func())
+ except:
+ self.exc = sys.exc_info()
@_evaluate_with_timeouts
def evaluate_expression(py_db, frame, expression, is_exec):
'''
- There are some changes in this function depending on whether it's an exec or an eval.
+ :param str expression:
+ The expression to be evaluated.
+
+ Note that if the expression is indented it's automatically dedented (based on the indentation
+ found on the first non-empty line).
+
+ i.e.: something as:
- When it's an exec (i.e.: is_exec==True):
- This function returns None.
- Any exception that happens during the evaluation is reraised.
- If the expression could actually be evaluated, the variable is printed to the console if not None.
+ `
+ def method():
+ a = 1
+ `
- When it's an eval (i.e.: is_exec==False):
- This function returns the result from the evaluation.
- If some exception happens in this case, the exception is caught and a ExceptionOnEvaluate is returned.
- Also, in this case we try to resolve name-mangling (i.e.: to be able to add a self.__my_var watch).
+ becomes:
+
+ `
+ def method():
+ a = 1
+ `
+
+ Also, it's possible to evaluate calls with a top-level await (currently this is done by
+ creating a new event loop in a new thread and making the evaluate at that thread -- note
+ that this is still done synchronously so the evaluation has to finish before this
+ function returns).
:param is_exec: determines if we should do an exec or an eval.
+ There are some changes in this function depending on whether it's an exec or an eval.
+
+ When it's an exec (i.e.: is_exec==True):
+ This function returns None.
+ Any exception that happens during the evaluation is reraised.
+ If the expression could actually be evaluated, the variable is printed to the console if not None.
+
+ When it's an eval (i.e.: is_exec==False):
+ This function returns the result from the evaluation.
+ If some exception happens in this case, the exception is caught and a ExceptionOnEvaluate is returned.
+ Also, in this case we try to resolve name-mangling (i.e.: to be able to add a self.__my_var watch).
+
+ :param py_db:
+ The debugger. Only needed if some top-level await is detected (for creating a
+ PyDBDaemonThread).
'''
if frame is None:
return
@@ -457,7 +533,7 @@ def evaluate_expression(py_db, frame, expression, is_exec):
if is_exec:
try:
- # try to make it an eval (if it is an eval we can print it, otherwise we'll exec it and
+ # Try to make it an eval (if it is an eval we can print it, otherwise we'll exec it and
# it will have whatever the user actually did)
compiled = compile_as_eval(expression)
except Exception:
@@ -465,18 +541,39 @@ def evaluate_expression(py_db, frame, expression, is_exec):
if compiled is None:
try:
- Exec(_expression_to_evaluate(expression), updated_globals, updated_locals)
+ compiled = _compile_as_exec(expression)
+ is_async = inspect.CO_COROUTINE & compiled.co_flags == inspect.CO_COROUTINE
+ if is_async:
+ t = _EvalAwaitInNewEventLoop(py_db, compiled, updated_globals, updated_locals)
+ t.start()
+ t.join()
+
+ if t.exc:
+ raise t.exc[1].with_traceback(t.exc[2])
+ else:
+ Exec(compiled, updated_globals, updated_locals)
finally:
# Update the globals even if it errored as it may have partially worked.
- _update_globals_and_locals(updated_globals, initial_globals, frame)
+ update_globals_and_locals(updated_globals, initial_globals, frame)
else:
- result = eval(compiled, updated_globals, updated_locals)
+ is_async = inspect.CO_COROUTINE & compiled.co_flags == inspect.CO_COROUTINE
+ if is_async:
+ t = _EvalAwaitInNewEventLoop(py_db, compiled, updated_globals, updated_locals)
+ t.start()
+ t.join()
+
+ if t.exc:
+ raise t.exc[1].with_traceback(t.exc[2])
+ else:
+ result = t.evaluated_value
+ else:
+ result = eval(compiled, updated_globals, updated_locals)
if result is not None: # Only print if it's not None (as python does)
sys.stdout.write('%s\n' % (result,))
return
else:
- ret = eval_in_context(expression, updated_globals, updated_locals)
+ ret = eval_in_context(expression, updated_globals, updated_locals, py_db)
try:
is_exception_returned = ret.__class__ == ExceptionOnEvaluate
except:
@@ -485,7 +582,7 @@ def evaluate_expression(py_db, frame, expression, is_exec):
if not is_exception_returned:
# i.e.: by using a walrus assignment (:=), expressions can change the locals,
# so, make sure that we save the locals back to the frame.
- _update_globals_and_locals(updated_globals, initial_globals, frame)
+ update_globals_and_locals(updated_globals, initial_globals, frame)
return ret
finally:
# Should not be kept alive if an exception happens and this frame is kept in the stack.
diff --git a/src/debugpy/_vendored/pydevd/pydevconsole.py b/src/debugpy/_vendored/pydevd/pydevconsole.py
index c118b8f2d..6b1378887 100644
--- a/src/debugpy/_vendored/pydevd/pydevconsole.py
+++ b/src/debugpy/_vendored/pydevd/pydevconsole.py
@@ -5,10 +5,7 @@
from _pydevd_bundle.pydevd_constants import IS_JYTHON
start_new_thread = thread.start_new_thread
-try:
- from code import InteractiveConsole
-except ImportError:
- from _pydevd_bundle.pydevconsole_code_for_ironpython import InteractiveConsole
+from _pydevd_bundle.pydevconsole_code import InteractiveConsole
compile_command = _code.compile_command
InteractiveInterpreter = _code.InteractiveInterpreter
diff --git a/src/debugpy/_vendored/pydevd/tests/test_pydevconsole.py b/src/debugpy/_vendored/pydevd/tests/test_pydevconsole.py
index 4372f1339..bbfc16efd 100644
--- a/src/debugpy/_vendored/pydevd/tests/test_pydevconsole.py
+++ b/src/debugpy/_vendored/pydevd/tests/test_pydevconsole.py
@@ -4,12 +4,14 @@
import pydevconsole
from _pydev_bundle.pydev_imports import xmlrpclib, SimpleXMLRPCServer
from _pydevd_bundle import pydevd_io
+from contextlib import contextmanager
+import pytest
try:
- raw_input
- raw_input_name = 'raw_input'
-except NameError:
- raw_input_name = 'input'
+ from ast import PyCF_ALLOW_TOP_LEVEL_AWAIT # @UnusedImport
+ CAN_EVALUATE_TOP_LEVEL_ASYNC = True
+except:
+ CAN_EVALUATE_TOP_LEVEL_ASYNC = False
#=======================================================================================================================
@@ -17,11 +19,15 @@
#=======================================================================================================================
class Test(unittest.TestCase):
- def test_console_hello(self):
+ @contextmanager
+ def interpreter(self):
self.original_stdout = sys.stdout
+ self.original_stderr = sys.stderr
sys.stdout = pydevd_io.IOBuf()
+ sys.stderr = pydevd_io.IOBuf()
try:
sys.stdout.encoding = sys.stdin.encoding
+ sys.stderr.encoding = sys.stdin.encoding
except AttributeError:
# In Python 3 encoding is not writable (whereas in Python 2 it doesn't exist).
pass
@@ -34,31 +40,50 @@ def test_console_hello(self):
from _pydev_bundle import pydev_localhost
interpreter = pydevconsole.InterpreterInterface(pydev_localhost.get_localhost(), client_port, threading.current_thread())
-
- (result,) = interpreter.hello("Hello pydevconsole")
- self.assertEqual(result, "Hello eclipse")
+ yield interpreter
+ except:
+ # if there's some error, print the output to the actual output.
+ self.original_stdout.write(sys.stdout.getvalue())
+ self.original_stderr.write(sys.stderr.getvalue())
+ raise
finally:
+ sys.stderr = self.original_stderr
sys.stdout = self.original_stdout
- def test_console_requests(self):
- self.original_stdout = sys.stdout
- sys.stdout = pydevd_io.IOBuf()
-
- try:
- client_port, _server_port = self.get_free_addresses()
- client_thread = self.start_client_thread(client_port) # @UnusedVariable
- import time
- time.sleep(.3) # let's give it some time to start the threads
+ def test_console_hello(self):
+ with self.interpreter() as interpreter:
+ (result,) = interpreter.hello("Hello pydevconsole")
+ self.assertEqual(result, "Hello eclipse")
- from _pydev_bundle import pydev_localhost
+ @pytest.mark.skipif(not CAN_EVALUATE_TOP_LEVEL_ASYNC, reason='Requires top-level async.')
+ def test_console_async(self):
+ with self.interpreter() as interpreter:
from _pydev_bundle.pydev_console_utils import CodeFragment
+ more = interpreter.add_exec(CodeFragment('''
+async def async_func(a):
+ return a
+'''))
+ assert not more
+ assert not sys.stderr.getvalue()
+ assert not sys.stdout.getvalue()
+
+ more = interpreter.add_exec(CodeFragment('''x = await async_func(1111)'''))
+ assert not more
+ assert not sys.stderr.getvalue()
+ assert not sys.stdout.getvalue()
+
+ more = interpreter.add_exec(CodeFragment('''print(x)'''))
+ assert not more
+ assert not sys.stderr.getvalue()
+ assert '1111' in sys.stdout.getvalue()
- interpreter = pydevconsole.InterpreterInterface(pydev_localhost.get_localhost(), client_port, threading.current_thread())
- sys.stdout = pydevd_io.IOBuf()
+ def test_console_requests(self):
+ with self.interpreter() as interpreter:
+ from _pydev_bundle.pydev_console_utils import CodeFragment
interpreter.add_exec(CodeFragment('class Foo:\n CONSTANT=1\n'))
interpreter.add_exec(CodeFragment('foo=Foo()'))
interpreter.add_exec(CodeFragment('foo.__doc__=None'))
- interpreter.add_exec(CodeFragment('val = %s()' % (raw_input_name,)))
+ interpreter.add_exec(CodeFragment('val = input()'))
interpreter.add_exec(CodeFragment('50'))
interpreter.add_exec(CodeFragment('print (val)'))
found = sys.stdout.getvalue().split()
@@ -129,8 +154,6 @@ def test_console_requests(self):
desc.find('Concatenate any number of strings.') >= 0 or
desc.find('bound method str.join') >= 0, # PyPy
"Could not recognize: %s" % (desc,))
- finally:
- sys.stdout = self.original_stdout
def start_client_thread(self, client_port):
@@ -234,7 +257,7 @@ def run(self):
server.execLine(' pass')
server.execLine('')
server.execLine('foo = Foo()')
- server.execLine('a = %s()' % (raw_input_name,))
+ server.execLine('a = input()')
server.execLine('print (a)')
initial = time.time()
while not client_thread.requested_input:
diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_evaluate_expression.py b/src/debugpy/_vendored/pydevd/tests_python/test_evaluate_expression.py
index 1adcce343..adc4ba261 100644
--- a/src/debugpy/_vendored/pydevd/tests_python/test_evaluate_expression.py
+++ b/src/debugpy/_vendored/pydevd/tests_python/test_evaluate_expression.py
@@ -1,5 +1,7 @@
+from _pydevd_bundle.pydevd_constants import IS_PY38_OR_GREATER, NULL
+from _pydevd_bundle.pydevd_xml import ExceptionOnEvaluate
+
import sys
-from _pydevd_bundle.pydevd_constants import IS_PY38_OR_GREATER
import pytest
SOME_LST = ["foo", "bar"]
@@ -130,3 +132,149 @@ def check(frame):
assert frame.f_locals['B'] == 6
check(next(iter(obtain_frame())))
+
+
+class _DummyPyDB(object):
+
+ def __init__(self):
+ self.created_pydb_daemon_threads = {}
+ self.timeout_tracker = NULL
+ self.multi_threads_single_notification = False
+
+
+try:
+ from ast import PyCF_ALLOW_TOP_LEVEL_AWAIT # @UnusedImport
+ CAN_EVALUATE_TOP_LEVEL_ASYNC = True
+except:
+ CAN_EVALUATE_TOP_LEVEL_ASYNC = False
+
+
+@pytest.mark.skipif(not CAN_EVALUATE_TOP_LEVEL_ASYNC, reason='Requires top-level async evaluation.')
+def test_evaluate_expression_async_exec(disable_critical_log):
+ py_db = _DummyPyDB()
+
+ async def async_call(a):
+ return a
+
+ async def main():
+ from _pydevd_bundle.pydevd_vars import evaluate_expression
+ a = 10
+ assert async_call is not None # Make sure it's in the locals.
+ frame = sys._getframe()
+ eval_txt = 'y = await async_call(a)'
+ evaluate_expression(py_db, frame, eval_txt, is_exec=True)
+ assert frame.f_locals['y'] == a
+
+ import asyncio
+ asyncio.run(main())
+
+
+@pytest.mark.skipif(not CAN_EVALUATE_TOP_LEVEL_ASYNC, reason='Requires top-level async evaluation.')
+def test_evaluate_expression_async_exec_as_eval(disable_critical_log):
+ py_db = _DummyPyDB()
+
+ async def async_call(a):
+ return a
+
+ async def main():
+ from _pydevd_bundle.pydevd_vars import evaluate_expression
+ assert async_call is not None # Make sure it's in the locals.
+ frame = sys._getframe()
+ eval_txt = 'await async_call(10)'
+ from io import StringIO
+ _original_stdout = sys.stdout
+ try:
+ stringio = sys.stdout = StringIO()
+ evaluate_expression(py_db, frame, eval_txt, is_exec=True)
+ finally:
+ sys.stdout = _original_stdout
+
+ # I.e.: Check that we printed the value obtained in the exec.
+ assert '10\n' in stringio.getvalue()
+
+ import asyncio
+ asyncio.run(main())
+
+
+@pytest.mark.skipif(not CAN_EVALUATE_TOP_LEVEL_ASYNC, reason='Requires top-level async evaluation.')
+def test_evaluate_expression_async_exec_error(disable_critical_log):
+ py_db = _DummyPyDB()
+
+ async def async_call(a):
+ raise RuntimeError('foobar')
+
+ async def main():
+ from _pydevd_bundle.pydevd_vars import evaluate_expression
+ assert async_call is not None # Make sure it's in the locals.
+ frame = sys._getframe()
+ eval_txt = 'y = await async_call(10)'
+ with pytest.raises(RuntimeError) as e:
+ evaluate_expression(py_db, frame, eval_txt, is_exec=True)
+ assert 'foobar' in str(e)
+ assert 'y' not in frame.f_locals
+
+ import asyncio
+ asyncio.run(main())
+
+
+@pytest.mark.skipif(not CAN_EVALUATE_TOP_LEVEL_ASYNC, reason='Requires top-level async evaluation.')
+def test_evaluate_expression_async_eval(disable_critical_log):
+ py_db = _DummyPyDB()
+
+ async def async_call(a):
+ return a
+
+ async def main():
+ from _pydevd_bundle.pydevd_vars import evaluate_expression
+ a = 10
+ assert async_call is not None # Make sure it's in the locals.
+ frame = sys._getframe()
+ eval_txt = 'await async_call(a)'
+ v = evaluate_expression(py_db, frame, eval_txt, is_exec=False)
+ if isinstance(v, ExceptionOnEvaluate):
+ raise v.result.with_traceback(v.tb)
+ assert v == a
+
+ import asyncio
+ asyncio.run(main())
+
+
+@pytest.mark.skipif(not CAN_EVALUATE_TOP_LEVEL_ASYNC, reason='Requires top-level async evaluation.')
+def test_evaluate_expression_async_eval_error(disable_critical_log):
+ py_db = _DummyPyDB()
+
+ async def async_call(a):
+ raise RuntimeError('foobar')
+
+ async def main():
+ from _pydevd_bundle.pydevd_vars import evaluate_expression
+ a = 10
+ assert async_call is not None # Make sure it's in the locals.
+ frame = sys._getframe()
+ eval_txt = 'await async_call(a)'
+ v = evaluate_expression(py_db, frame, eval_txt, is_exec=False)
+ assert isinstance(v, ExceptionOnEvaluate)
+ assert 'foobar' in str(v.result)
+
+ import asyncio
+ asyncio.run(main())
+
+
+def test_evaluate_expression_name_mangling(disable_critical_log):
+ from _pydevd_bundle.pydevd_vars import evaluate_expression
+
+ class SomeObj(object):
+
+ def __init__(self):
+ self.__value = 10
+ self.frame = sys._getframe()
+
+ obj = SomeObj()
+ frame = obj.frame
+
+ eval_txt = '''self.__value'''
+ v = evaluate_expression(None, frame, eval_txt, is_exec=False)
+ if isinstance(v, ExceptionOnEvaluate):
+ raise v.result.with_traceback(v.tb)
+
+ assert v == 10