Remove debugpy.compat, custom !j formatter for JSON, and related helpers.

This commit is contained in:
Pavel Minaev 2022-04-12 18:30:48 -07:00 committed by Pavel Minaev
parent 1965b47034
commit 0f428178b0
31 changed files with 240 additions and 408 deletions

View file

@ -26,7 +26,6 @@ import codecs
import os
from debugpy import _version
from debugpy.common import compat
# Expose debugpy.server API from subpackage, but do not actually import it unless
@ -111,8 +110,7 @@ def listen(address):
return api.listen(address)
@compat.kwonly
def connect(address, access_token=None):
def connect(address, *, access_token=None):
"""Tells an existing debug adapter instance that is listening on the
specified address to debug this process.

View file

@ -25,7 +25,7 @@ def main(args):
atexit.register(stderr.close)
from debugpy import adapter
from debugpy.common import compat, json, log, sockets
from debugpy.common import json, log, sockets
from debugpy.adapter import clients, servers, sessions
if args.for_server is not None:
@ -51,7 +51,7 @@ def main(args):
servers.access_token = args.server_access_token
if args.for_server is None:
adapter.access_token = compat.force_str(codecs.encode(os.urandom(32), "hex"))
adapter.access_token = codecs.encode(os.urandom(32), "hex").decode("ascii")
endpoints = {}
try:

View file

@ -8,8 +8,7 @@ import sys
import debugpy
from debugpy import adapter, common, launcher
from debugpy.common import compat, json, log, messaging, sockets
from debugpy.common.compat import unicode
from debugpy.common import json, log, messaging, sockets
from debugpy.adapter import components, servers, sessions
@ -202,7 +201,7 @@ class Client(components.Component):
servers.dont_wait_for_first_connection()
self.session.debug_options = debug_options = set(
request("debugOptions", json.array(unicode))
request("debugOptions", json.array(str))
)
f(self, request)
@ -293,7 +292,7 @@ class Client(components.Component):
if self.session.id != 1 or len(servers.connections()):
raise request.cant_handle('"attach" expected')
debug_options = set(request("debugOptions", json.array(unicode)))
debug_options = set(request("debugOptions", json.array(str)))
# Handling of properties that can also be specified as legacy "debugOptions" flags.
# If property is explicitly set to false, but the flag is in "debugOptions", treat
@ -326,29 +325,29 @@ class Client(components.Component):
)
elif "pythonPath" in request:
python_key = "pythonPath"
python = request(python_key, json.array(unicode, vectorize=True, size=(0,)))
python = request(python_key, json.array(str, vectorize=True, size=(0,)))
if not len(python):
python = [compat.filename(sys.executable)]
python = [sys.executable]
python += request("pythonArgs", json.array(unicode, size=(0,)))
python += request("pythonArgs", json.array(str, size=(0,)))
request.arguments["pythonArgs"] = python[1:]
request.arguments["python"] = python
launcher_python = request("debugLauncherPython", unicode, optional=True)
launcher_python = request("debugLauncherPython", str, optional=True)
if launcher_python == ():
launcher_python = python[0]
program = module = code = ()
if "program" in request:
program = request("program", unicode)
program = request("program", str)
args = [program]
request.arguments["processName"] = program
if "module" in request:
module = request("module", unicode)
module = request("module", str)
args = ["-m", module]
request.arguments["processName"] = module
if "code" in request:
code = request("code", json.array(unicode, vectorize=True, size=(1,)))
code = request("code", json.array(str, vectorize=True, size=(1,)))
args = ["-c", "\n".join(code)]
request.arguments["processName"] = "-c"
@ -367,10 +366,10 @@ class Client(components.Component):
"argsExpansion", json.enum("shell", "none", optional=True)
)
if args_expansion == "shell":
args += request("args", json.array(unicode))
args += request("args", json.array(str))
request.arguments.pop("args", None)
cwd = request("cwd", unicode, optional=True)
cwd = request("cwd", str, optional=True)
if cwd == ():
# If it's not specified, but we're launching a file rather than a module,
# and the specified path has a directory in it, use that.
@ -421,11 +420,11 @@ class Client(components.Component):
if self.session.no_debug:
raise request.isnt_valid('"noDebug" is not supported for "attach"')
host = request("host", unicode, optional=True)
host = request("host", str, optional=True)
port = request("port", int, optional=True)
listen = request("listen", dict, optional=True)
connect = request("connect", dict, optional=True)
pid = request("processId", (int, unicode), optional=True)
pid = request("processId", (int, str), optional=True)
sub_pid = request("subProcessId", int, optional=True)
if host != () or port != ():
@ -494,7 +493,7 @@ class Client(components.Component):
pid = int(pid)
except Exception:
raise request.isnt_valid('"processId" must be parseable as int')
debugpy_args = request("debugpyArgs", json.array(unicode))
debugpy_args = request("debugpyArgs", json.array(str))
servers.inject(pid, debugpy_args)
timeout = common.PROCESS_SPAWN_TIMEOUT
pred = lambda conn: conn.pid == pid

View file

@ -7,7 +7,7 @@ import subprocess
import sys
from debugpy import adapter, common
from debugpy.common import compat, json, log, messaging, sockets
from debugpy.common import json, log, messaging, sockets
from debugpy.adapter import components, servers
@ -112,7 +112,7 @@ def spawn_debuggee(
cmdline += args
if log.log_dir is not None:
env[str("DEBUGPY_LOG_DIR")] = compat.filename_str(log.log_dir)
env[str("DEBUGPY_LOG_DIR")] = log.log_dir
if log.stderr.levels != {"warning", "error"}:
env[str("DEBUGPY_LOG_STDERR")] = str(" ".join(log.stderr.levels))
@ -121,7 +121,7 @@ def spawn_debuggee(
try:
for i, arg in enumerate(cmdline):
try:
cmdline[i] = compat.filename_str(arg)
cmdline[i] = arg
except UnicodeEncodeError as exc:
raise start_request.cant_handle(
"Invalid command line argument {0}: {1}", json.repr(arg), exc

View file

@ -10,7 +10,7 @@ import time
import debugpy
from debugpy import adapter
from debugpy.common import compat, json, log, messaging, sockets
from debugpy.common import json, log, messaging, sockets
from debugpy.adapter import components
@ -445,7 +445,7 @@ def inject(pid, debugpy_args):
cmdline = [
sys.executable,
compat.filename(os.path.dirname(debugpy.__file__)),
os.path.dirname(debugpy.__file__),
"--connect",
host + ":" + str(port),
]

View file

@ -1,210 +0,0 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# # Licensed under the MIT License. See LICENSE in the project root
# for license information.
"""Python 2/3 compatibility helpers.
"""
import functools
import inspect
import itertools
import sys
if sys.version_info[0] < 3:
# Py2
import __builtin__ as builtins # noqa
from __builtin__ import unicode, bytes, xrange, reload # noqa
izip = itertools.izip
import Queue as queue # noqa
def force_str(s, encoding="ascii", errors="strict"):
"""Converts s to str (which is bytes on Python 2, and unicode on Python 3), using
the provided encoding if necessary. If s is already str, it is returned as is.
If errors="strict", str is bytes, and s is str, its encoding is verified by decoding
it; UnicodeError is raised if it cannot be decoded.
"""
return force_bytes(s, encoding, errors)
else:
# Py3
import builtins # noqa
from builtins import bytes # noqa
unicode = str
xrange = range
izip = zip
from importlib import reload # noqa
import queue # noqa
def force_str(s, encoding="ascii", errors="strict"):
"""Converts s to str (which is bytes on Python 2, and unicode on Python 3), using
the provided encoding if necessary. If s is already str, it is returned as is.
If errors="strict", str is bytes, and s is str, its encoding is verified by decoding
it; UnicodeError is raised if it cannot be decoded.
"""
return force_unicode(s, encoding, errors)
def force_unicode(s, encoding, errors="strict"):
"""Converts s to Unicode, using the provided encoding. If s is already Unicode,
it is returned as is.
"""
return s.decode(encoding, errors) if isinstance(s, bytes) else unicode(s)
def force_bytes(s, encoding, errors="strict"):
"""Converts s to bytes, using the provided encoding. If s is already bytes,
it is returned as is.
If errors="strict" and s is bytes, its encoding is verified by decoding it;
UnicodeError is raised if it cannot be decoded.
"""
if isinstance(s, unicode):
return s.encode(encoding, errors)
else:
s = bytes(s)
if errors == "strict":
# Return value ignored - invoked solely for verification.
s.decode(encoding, errors)
return s
def force_ascii(s, errors="strict"):
"""Same as force_bytes(s, "ascii", errors)
"""
return force_bytes(s, "ascii", errors)
def force_utf8(s, errors="strict"):
"""Same as force_bytes(s, "utf8", errors)
"""
return force_bytes(s, "utf8", errors)
def filename(s, errors="strict"):
"""Same as force_unicode(s, sys.getfilesystemencoding(), errors)
"""
return force_unicode(s, sys.getfilesystemencoding(), errors)
def filename_bytes(s, errors="strict"):
"""Same as force_bytes(s, sys.getfilesystemencoding(), errors)
"""
return force_bytes(s, sys.getfilesystemencoding(), errors)
def filename_str(s, errors="strict"):
"""Same as force_str(s, sys.getfilesystemencoding(), errors)
"""
return force_str(s, sys.getfilesystemencoding(), errors)
def nameof(obj, quote=False):
"""Returns the most descriptive name of a Python module, class, or function,
as a Unicode string
If quote=True, name is quoted with repr().
Best-effort, but guaranteed to not fail - always returns something.
"""
try:
name = obj.__qualname__
except Exception:
try:
name = obj.__name__
except Exception:
# Fall back to raw repr(), and skip quoting.
try:
name = repr(obj)
except Exception:
return "<unknown>"
else:
quote = False
if quote:
try:
name = repr(name)
except Exception:
pass
return force_unicode(name, "utf-8", "replace")
def unicode_repr(obj):
"""Like repr(), but guarantees that the result is Unicode even on Python 2.
"""
return force_unicode(repr(obj), "ascii")
def srcnameof(obj):
"""Returns the most descriptive name of a Python module, class, or function,
including source information (filename and linenumber), if available.
Best-effort, but guaranteed to not fail - always returns something.
"""
name = nameof(obj, quote=True)
# Get the source information if possible.
try:
src_file = filename(inspect.getsourcefile(obj), "replace")
except Exception:
pass
else:
name += " (file {0!r}".format(src_file)
try:
_, src_lineno = inspect.getsourcelines(obj)
except Exception:
pass
else:
name += ", line {0}".format(src_lineno)
name += ")"
return name
def kwonly(f):
"""Makes all arguments with default values keyword-only.
If the default value is kwonly.required, then the argument must be specified.
"""
try:
inspect.getfullargspec
except AttributeError:
arg_names, args_name, kwargs_name, arg_defaults = inspect.getargspec(f)
else:
arg_names, args_name, kwargs_name, arg_defaults, _, _, _ = inspect.getfullargspec(
f
)
assert args_name is None and kwargs_name is None
argc = len(arg_names)
pos_argc = argc - len(arg_defaults)
required_names = {
name
for name, val in zip(arg_names[pos_argc:], arg_defaults)
if val is kwonly.required
}
@functools.wraps(f)
def kwonly_f(*args, **kwargs):
if len(args) > pos_argc:
raise TypeError("too many positional arguments")
if not required_names.issubset(kwargs):
missing_names = required_names.difference(kwargs)
missing_names = ", ".join(repr(s) for s in missing_names)
raise TypeError("missing required keyword-only arguments: " + missing_names)
return f(*args, **kwargs)
return kwonly_f
kwonly.required = object()

View file

@ -5,6 +5,7 @@
"""Improved JSON serialization.
"""
import builtins
import json
import operator
@ -23,9 +24,10 @@ class JsonEncoder(json.JSONEncoder):
try:
get_state = value.__getstate__
except AttributeError:
return super(JsonEncoder, self).default(value)
pass
else:
return get_state()
return super(JsonEncoder, self).default(value)
class JsonObject(object):
@ -40,10 +42,14 @@ class JsonObject(object):
"""The default encoder used by __format__ when format_spec is empty."""
def __init__(self, value):
assert not isinstance(value, JsonObject)
self.value = value
def __getstate__(self):
raise NotImplementedError
def __repr__(self):
return repr(self.value)
return builtins.repr(self.value)
def __str__(self):
return format(self)

View file

@ -14,7 +14,7 @@ import threading
import traceback
import debugpy
from debugpy.common import compat, json, timestamp, util
from debugpy.common import json, timestamp, util
LEVELS = ("debug", "info", "warning", "error")
@ -304,7 +304,7 @@ def describe_environment(header):
except Exception:
swallow_exception(
"Error evaluating {0}",
repr(expr) if expr else compat.srcnameof(get_paths),
repr(expr) if expr else util.srcnameof(get_paths),
)
return
@ -331,7 +331,7 @@ def describe_environment(header):
p
for p in sys.path
if os.path.exists(p)
and os.path.basename(p) == compat.filename_str("site-packages")
and os.path.basename(p) == "site-packages"
]
report_paths(lambda: site_packages, "sys.path (site-packages)")

View file

@ -18,8 +18,7 @@ import socket
import sys
import threading
from debugpy.common import compat, json, log
from debugpy.common.compat import unicode
from debugpy.common import json, log, util
class JsonIOError(IOError):
@ -157,16 +156,10 @@ class JsonIOStream(object):
finally:
self._cleanup()
except Exception:
# On Python 2, close() will raise an exception if there is a concurrent
# read() or write(), which is a common and expected occurrence with
# JsonMessageChannel, so don't even bother logging it.
log.reraise_exception("Error while closing {0} message stream", self.name)
def _log_message(self, dir, data, logger=log.debug):
format_string = "{0} {1} " + (
"{2:indent=None}" if isinstance(data, list) else "{2}"
)
return logger(format_string, self.name, dir, json.repr(data))
return logger("{0} {1} {2}", self.name, dir, data)
def _read_line(self, reader):
line = b""
@ -277,7 +270,7 @@ class JsonIOStream(object):
Value is written as encoded by encoder.encode().
"""
if self._closed:
# Don't log this - it's a common pattern to write to a stream while
# anticipating EOFError from it in case it got closed concurrently.
@ -293,9 +286,8 @@ class JsonIOStream(object):
try:
body = encoder.encode(value)
except Exception:
self._log_message("<--", value, logger=log.reraise_exception)
if not isinstance(body, bytes):
body = body.encode("utf-8")
self._log_message("<--", repr(value), logger=log.reraise_exception)
body = body.encode("utf-8")
header = f"Content-Length: {len(body)}\r\n\r\n".encode("ascii")
data = header + body
@ -355,7 +347,10 @@ class MessageDict(collections.OrderedDict):
"""
def __repr__(self):
return json.repr(self)
try:
return format(json.repr(self))
except Exception:
return super().__repr__()
def __call__(self, key, validate, optional=False):
"""Like get(), but with validation.
@ -583,7 +578,7 @@ class Event(Message):
@staticmethod
def _parse(channel, message_dict):
seq = message_dict("seq", int)
event = message_dict("event", unicode)
event = message_dict("event", str)
body = message_dict("body", _payload)
message = Event(channel, seq, event, body, json=message_dict)
channel._enqueue_handlers(message, message._handle)
@ -595,20 +590,20 @@ class Event(Message):
try:
result = handler(self)
assert result is None, \
f"Handler {compat.srcnameof(handler)} tried to respond to {self.describe()}."
f"Handler {util.srcnameof(handler)} tried to respond to {self.describe()}."
except MessageHandlingError as exc:
if not exc.applies_to(self):
raise
log.error(
"Handler {0}\ncouldn't handle {1}:\n{2}",
compat.srcnameof(handler),
util.srcnameof(handler),
self.describe(),
str(exc),
)
except Exception:
log.reraise_exception(
"Handler {0}\ncouldn't handle {1}:",
compat.srcnameof(handler),
util.srcnameof(handler),
self.describe(),
)
@ -688,16 +683,7 @@ class Request(Message):
if isinstance(body, Exception):
d["success"] = False
err_text = str(body)
try:
err_text = compat.force_unicode(err_text, "utf-8")
except Exception:
# On Python 2, the error message might not be Unicode, and we don't
# really know what encoding it is. So if treating it as UTF-8 failed,
# use repr() as a fallback - it should escape all non-ASCII chars in
# the string.
err_text = compat.force_unicode(repr(body), "ascii", errors="replace")
d["message"] = err_text
d["message"] = str(body)
else:
d["success"] = True
if body is not None and body != {}:
@ -710,7 +696,7 @@ class Request(Message):
@staticmethod
def _parse(channel, message_dict):
seq = message_dict("seq", int)
command = message_dict("command", unicode)
command = message_dict("command", str)
arguments = message_dict("arguments", _payload)
message = Request(channel, seq, command, arguments, json=message_dict)
channel._enqueue_handlers(message, message._handle)
@ -727,7 +713,7 @@ class Request(Message):
result = exc
log.error(
"Handler {0}\ncouldn't handle {1}:\n{2}",
compat.srcnameof(handler),
util.srcnameof(handler),
self.describe(),
str(exc),
)
@ -736,7 +722,7 @@ class Request(Message):
assert self.response is None, (
"Handler {0} for {1} must not return NO_RESPONSE if it has already "
"invoked request.respond().".format(
compat.srcnameof(handler),
util.srcnameof(handler),
self.describe()
)
)
@ -744,7 +730,7 @@ class Request(Message):
assert result is None or result is self.response.body, (
"Handler {0} for {1} must not return a response body if it has "
"already invoked request.respond().".format(
compat.srcnameof(handler),
util.srcnameof(handler),
self.describe()
)
)
@ -752,7 +738,7 @@ class Request(Message):
assert result is not None, (
"Handler {0} for {1} must either call request.respond() before it "
"returns, or return the response body, or return NO_RESPONSE.".format(
compat.srcnameof(handler),
util.srcnameof(handler),
self.describe()
)
)
@ -761,14 +747,14 @@ class Request(Message):
except NoMoreMessages:
log.warning(
"Channel was closed before the response from handler {0} to {1} could be sent",
compat.srcnameof(handler),
util.srcnameof(handler),
self.describe(),
)
except Exception:
log.reraise_exception(
"Handler {0}\ncouldn't handle {1}:",
compat.srcnameof(handler),
util.srcnameof(handler),
self.describe(),
)
@ -844,14 +830,14 @@ class OutgoingRequest(Request):
raise
log.error(
"Handler {0}\ncouldn't handle {1}:\n{2}",
compat.srcnameof(handler),
util.srcnameof(handler),
response.describe(),
str(exc),
)
except Exception:
log.reraise_exception(
"Handler {0}\ncouldn't handle {1}:",
compat.srcnameof(handler),
util.srcnameof(handler),
response.describe(),
)
@ -937,13 +923,13 @@ class Response(Message):
def _parse(channel, message_dict, body=None):
seq = message_dict("seq", int) if (body is None) else None
request_seq = message_dict("request_seq", int)
command = message_dict("command", unicode)
command = message_dict("command", str)
success = message_dict("success", bool)
if body is None:
if success:
body = message_dict("body", _payload)
else:
error_message = message_dict("message", unicode)
error_message = message_dict("message", str)
exc_type = MessageHandlingError
if error_message.startswith(InvalidMessageError.PREFIX):
error_message = error_message[len(InvalidMessageError.PREFIX) :]
@ -1326,7 +1312,7 @@ class JsonMessageChannel(object):
log.debug("Exiting message loop for channel {0}: {1}", self, exc)
with self:
# Generate dummy responses for all outstanding requests.
err_message = compat.force_unicode(str(exc), "utf-8", errors="replace")
err_message = str(exc)
# Response._parse() will remove items from _sent_requests, so
# make a snapshot before iterating.
@ -1502,7 +1488,7 @@ class JsonMessageChannel(object):
raise AttributeError(
"handler object {0} for channel {1} has no handler for {2} {3!r}".format(
compat.srcnameof(handlers),
util.srcnameof(handlers),
self,
type,
name,
@ -1516,7 +1502,7 @@ class JsonMessageChannel(object):
except Exception:
log.reraise_exception(
"Handler {0}\ncouldn't handle disconnect from {1}:",
compat.srcnameof(handler),
util.srcnameof(handler),
self,
)

View file

@ -2,11 +2,10 @@
# Licensed under the MIT License. See LICENSE in the project root
# for license information.
import inspect
import os
import sys
from debugpy.common import compat
def evaluate(code, path=__file__, mode="eval"):
# Setting file path here to avoid breaking here if users have set
@ -59,8 +58,97 @@ class Env(dict):
tail = ""
self[key] = entry + tail
def for_popen(self):
"""Returns a copy of this dict, with all strings converted to the type
suitable for subprocess.Popen() and other similar APIs.
"""
return {compat.filename_str(k): compat.filename_str(v) for k, v in self.items()}
def force_str(s, encoding, errors="strict"):
"""Converts s to str, using the provided encoding. If s is already str,
it is returned as is.
"""
return s.decode(encoding, errors) if isinstance(s, bytes) else str(s)
def force_bytes(s, encoding, errors="strict"):
"""Converts s to bytes, using the provided encoding. If s is already bytes,
it is returned as is.
If errors="strict" and s is bytes, its encoding is verified by decoding it;
UnicodeError is raised if it cannot be decoded.
"""
if isinstance(s, str):
return s.encode(encoding, errors)
else:
s = bytes(s)
if errors == "strict":
# Return value ignored - invoked solely for verification.
s.decode(encoding, errors)
return s
def force_ascii(s, errors="strict"):
"""Same as force_bytes(s, "ascii", errors)
"""
return force_bytes(s, "ascii", errors)
def force_utf8(s, errors="strict"):
"""Same as force_bytes(s, "utf8", errors)
"""
return force_bytes(s, "utf8", errors)
def nameof(obj, quote=False):
"""Returns the most descriptive name of a Python module, class, or function,
as a Unicode string
If quote=True, name is quoted with repr().
Best-effort, but guaranteed to not fail - always returns something.
"""
try:
name = obj.__qualname__
except Exception:
try:
name = obj.__name__
except Exception:
# Fall back to raw repr(), and skip quoting.
try:
name = repr(obj)
except Exception:
return "<unknown>"
else:
quote = False
if quote:
try:
name = repr(name)
except Exception:
pass
return force_str(name, "utf-8", "replace")
def srcnameof(obj):
"""Returns the most descriptive name of a Python module, class, or function,
including source information (filename and linenumber), if available.
Best-effort, but guaranteed to not fail - always returns something.
"""
name = nameof(obj, quote=True)
# Get the source information if possible.
try:
src_file = inspect.getsourcefile(obj)
except Exception:
pass
else:
name += f" (file {src_file!r}"
try:
_, src_lineno = inspect.getsourcelines(obj)
except Exception:
pass
else:
name += f", line {src_lineno}"
name += ")"
return name

View file

@ -12,7 +12,7 @@ import sys
import threading
from debugpy import launcher
from debugpy.common import log, messaging, compat
from debugpy.common import log, messaging
from debugpy.launcher import output
if sys.platform == "win32":
@ -147,7 +147,7 @@ def spawn(process_name, cmdline, env, redirect_output):
"isLocalProcess": True,
"systemProcessId": process.pid,
"name": process_name,
"pointerSize": struct.calcsize(compat.force_str("P")) * 8,
"pointerSize": struct.calcsize("P") * 8,
},
)

View file

@ -7,13 +7,12 @@ import sys
import debugpy
from debugpy import launcher
from debugpy.common import compat, json
from debugpy.common.compat import unicode
from debugpy.common import json
from debugpy.launcher import debuggee
def launch_request(request):
debug_options = set(request("debugOptions", json.array(unicode)))
debug_options = set(request("debugOptions", json.array(str)))
# Handling of properties that can also be specified as legacy "debugOptions" flags.
# If property is explicitly set to false, but the flag is in "debugOptions", treat
@ -36,13 +35,13 @@ def launch_request(request):
return value
python = request("python", json.array(unicode, size=(1,)))
python = request("python", json.array(str, size=(1,)))
cmdline = list(python)
if not request("noDebug", json.default(False)):
port = request("port", int)
cmdline += [
compat.filename(os.path.dirname(debugpy.__file__)),
os.path.dirname(debugpy.__file__),
"--connect",
launcher.adapter_host + ":" + str(port),
]
@ -58,11 +57,11 @@ def launch_request(request):
)
cmdline += ["--configure-qt", qt_mode]
adapter_access_token = request("adapterAccessToken", unicode, optional=True)
adapter_access_token = request("adapterAccessToken", str, optional=True)
if adapter_access_token != ():
cmdline += ["--adapter-access-token", compat.filename(adapter_access_token)]
cmdline += ["--adapter-access-token", adapter_access_token]
debugpy_args = request("debugpyArgs", json.array(unicode))
debugpy_args = request("debugpyArgs", json.array(str))
cmdline += debugpy_args
# Further arguments can come via two channels: the launcher's own command line, or
@ -70,12 +69,12 @@ def launch_request(request):
# Arguments for debugpy (such as -m) always come via CLI, but those specified by the
# user via "args" are passed differently by the adapter depending on "argsExpansion".
cmdline += sys.argv[1:]
cmdline += request("args", json.array(unicode))
cmdline += request("args", json.array(str))
process_name = request("processName", compat.filename(sys.executable))
process_name = request("processName", sys.executable)
env = os.environ.copy()
env_changes = request("env", json.object((unicode, type(None))))
env_changes = request("env", json.object((str, type(None))))
if sys.platform == "win32":
# Environment variables are case-insensitive on Win32, so we need to normalize
# both dicts to make sure that env vars specified in the debug configuration

View file

@ -11,7 +11,7 @@ import threading
import debugpy
from debugpy import adapter
from debugpy.common import compat, json, log, sockets
from debugpy.common import json, log, sockets
from _pydevd_bundle.pydevd_constants import get_global_debugger
from pydevd_file_utils import absolute_path
@ -149,7 +149,7 @@ def listen(address, settrace_kwargs):
import subprocess
server_access_token = compat.force_str(codecs.encode(os.urandom(32), "hex"))
server_access_token = codecs.encode(os.urandom(32), "hex").decode("ascii")
try:
endpoints_listener = sockets.create_server("127.0.0.1", 0, timeout=10)

View file

@ -16,7 +16,7 @@ assert "pydevd" in sys.modules
import pydevd
import debugpy
from debugpy.common import compat, log
from debugpy.common import log
from debugpy.server import api
@ -42,7 +42,7 @@ class Options(object):
address = None
log_to = None
log_to_stderr = False
target = None # unicode
target = None
target_kind = None
wait_for_client = False
adapter_access_token = None
@ -203,7 +203,7 @@ def parse_argv():
except StopIteration:
raise ValueError("missing target: " + TARGET)
switch = compat.filename(arg)
switch = arg
if not switch.startswith("-"):
switch = ""
for pattern, placeholder, action in switches:
@ -244,7 +244,7 @@ def start_debugging(argv_0):
# We need to set up sys.argv[0] before invoking either listen() or connect(),
# because they use it to report the "process" event. Thus, we can't rely on
# run_path() and run_module() doing that, even though they will eventually.
sys.argv[0] = compat.filename_str(argv_0)
sys.argv[0] = argv_0
log.debug("sys.argv after patching: {0!r}", sys.argv)
@ -265,15 +265,13 @@ def run_file():
target = options.target
start_debugging(target)
target_as_str = compat.filename_str(target)
# run_path has one difference with invoking Python from command-line:
# if the target is a file (rather than a directory), it does not add its
# parent directory to sys.path. Thus, importing other modules from the
# same directory is broken unless sys.path is patched here.
if os.path.isfile(target_as_str):
dir = os.path.dirname(target_as_str)
if os.path.isfile(target):
dir = os.path.dirname(target)
sys.path.insert(0, dir)
else:
log.debug("Not a file: {0!r}", target)
@ -281,7 +279,7 @@ def run_file():
log.describe_environment("Pre-launch environment:")
log.info("Running file {0!r}", target)
runpy.run_path(target_as_str, run_name=compat.force_str("__main__"))
runpy.run_path(target, run_name="__main__")
def run_module():
@ -292,20 +290,14 @@ def run_module():
# We want to do the same thing that run_module() would do here, without
# actually invoking it.
argv_0 = sys.argv[0]
target_as_str = compat.filename_str(options.target)
try:
spec = find_spec(target_as_str)
spec = find_spec(options.target)
if spec is not None:
argv_0 = spec.origin
except Exception:
log.swallow_exception("Error determining module path for sys.argv")
start_debugging(argv_0)
# On Python 2, module name must be a non-Unicode string, because it ends up
# a part of module's __package__, and Python will refuse to run the module
# if __package__ is Unicode.
log.describe_environment("Pre-launch environment:")
log.info("Running module {0!r}", options.target)
@ -318,9 +310,9 @@ def run_module():
run_module_as_main = runpy._run_module_as_main
except AttributeError:
log.warning("runpy._run_module_as_main is missing, falling back to run_module.")
runpy.run_module(target_as_str, alter_sys=True)
runpy.run_module(options.target, alter_sys=True)
else:
run_module_as_main(target_as_str, alter_argv=True)
run_module_as_main(options.target, alter_argv=True)
def run_code():

View file

@ -8,8 +8,6 @@
import py.path
import re
from debugpy.common import compat
_marked_line_numbers_cache = {}
@ -34,13 +32,13 @@ def get_marked_line_numbers(path):
except KeyError:
pass
# Read as bytes, to avoid decoding errors on Python 3.
# Read as bytes to avoid decoding errors.
with open(path, "rb") as f:
lines = {}
for i, line in enumerate(f):
match = re.search(br"#\s*@(.+?)\s*$", line)
if match:
markers = compat.force_unicode(match.group(1), "ascii")
markers = match.group(1).decode("ascii")
for marker in markers.split(","):
lines[marker] = i + 1

View file

@ -86,27 +86,27 @@ class CapturedOutput(object):
def stdout(self, encoding=None):
"""Returns stdout captured from the debugged process, as a single string.
If encoding is None, returns bytes. Otherwise, returns unicode.
If encoding is None, returns bytes. Otherwise, returns str.
"""
return self._output("stdout", encoding, lines=False)
def stderr(self, encoding=None):
"""Returns stderr captured from the debugged process, as a single string.
If encoding is None, returns bytes. Otherwise, returns unicode.
If encoding is None, returns bytes. Otherwise, returns str.
"""
return self._output("stderr", encoding, lines=False)
def stdout_lines(self, encoding=None):
"""Returns stdout captured from the debugged process, as a list of lines.
If encoding is None, each line is bytes. Otherwise, each line is unicode.
If encoding is None, each line is bytes. Otherwise, each line is str.
"""
return self._output("stdout", encoding, lines=True)
def stderr_lines(self, encoding=None):
"""Returns stderr captured from the debugged process, as a list of lines.
If encoding is None, each line is bytes. Otherwise, each line is unicode.
If encoding is None, each line is bytes. Otherwise, each line is str.
"""
return self._output("stderr", encoding, lines=True)

View file

@ -56,7 +56,7 @@ import pytest
import sys
import debugpy
from debugpy.common import compat, json, log
from debugpy.common import json, log
from tests import net, timeline
from tests.debug import session
from tests.patterns import some
@ -218,7 +218,7 @@ def attach_connect(session, target, method, cwd=None, wait=True, log_dir=None):
args = [
os.path.dirname(debugpy.__file__),
"--listen",
compat.filename_str(host) + ":" + str(port),
f"{host}:{port}",
]
if wait:
args += ["--wait-for-client"]
@ -282,7 +282,7 @@ def attach_listen(session, target, method, cwd=None, log_dir=None):
args = [
os.path.dirname(debugpy.__file__),
"--connect",
compat.filename_str(host) + ":" + str(port),
f"{host}:{port}",
]
if log_dir is not None:
args += ["--log-to", log_dir]

View file

@ -12,8 +12,7 @@ import sys
import time
import debugpy.adapter
from debugpy.common import compat, json, log, messaging, sockets, util
from debugpy.common.compat import unicode
from debugpy.common import json, log, messaging, sockets, util
import tests
from tests import code, timeline, watchdog
from tests.debug import comms, config, output
@ -343,16 +342,15 @@ class Session(object):
assert not len(self.captured_output - {"stdout", "stderr"})
args = [exe] + [
compat.filename_str(s.strpath if isinstance(s, py.path.local) else s)
str(s.strpath if isinstance(s, py.path.local) else s)
for s in args
]
cwd = compat.filename_str(cwd) if isinstance(cwd, py.path.local) else cwd
cwd = cwd.strpath if isinstance(cwd, py.path.local) else cwd
env = self._make_env(self.spawn_debuggee.env, codecov=False)
env["DEBUGPY_ADAPTER_ENDPOINTS"] = self.adapter_endpoints = (
self.tmpdir / "adapter_endpoints"
)
self.adapter_endpoints = self.tmpdir / "adapter_endpoints"
env["DEBUGPY_ADAPTER_ENDPOINTS"] = self.adapter_endpoints.strpath
if setup is not None:
env["DEBUGPY_TEST_DEBUGGEE_SETUP"] = setup
@ -376,7 +374,7 @@ class Session(object):
self.debuggee = psutil.Popen(
args,
cwd=cwd,
env=env.for_popen(),
env=env,
bufsize=0,
stdin=subprocess.PIPE,
**popen_fds
@ -416,7 +414,7 @@ class Session(object):
bufsize=0,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
env=env.for_popen(),
env=env,
)
log.info("Spawned {0} with PID={1}", self.adapter_id, self.adapter.pid)
watchdog.register_spawn(self.adapter.pid, self.adapter_id)
@ -497,9 +495,9 @@ class Session(object):
def _process_request(self, request):
self.timeline.record_request(request, block=False)
if request.command == "runInTerminal":
args = request("args", json.array(unicode))
args = request("args", json.array(str))
cwd = request("cwd", ".")
env = request("env", json.object(unicode))
env = request("env", json.object(str))
try:
return self.run_in_terminal(args, cwd, env)
except Exception as exc:
@ -578,7 +576,7 @@ class Session(object):
all the "output" events received for that category so far.
"""
events = self.all_events("output", some.dict.containing({"category": category}))
return "".join(event("output", unicode) for event in events)
return "".join(event("output", str) for event in events)
def _request_start(self, method):
self.config.normalize()
@ -718,7 +716,7 @@ class Session(object):
)("variables", json.array())
variables = collections.OrderedDict(
((v("name", unicode), v) for v in variables)
((v("name", str), v) for v in variables)
)
if varnames:
assert set(varnames) <= set(variables.keys())
@ -765,7 +763,7 @@ class Session(object):
expected_stopped["text"] = expected_text
if expected_description is not None:
expected_stopped["description"] = expected_description
if stopped("reason", unicode) not in [
if stopped("reason", str) not in [
"step",
"exception",
"breakpoint",

View file

@ -5,7 +5,7 @@
import py
import os
from debugpy.common import compat, json
from debugpy.common import json
from tests.patterns import some
@ -102,7 +102,7 @@ class Program(Target):
def _get_relative_program(self):
assert self._cwd
relative_filename = compat.filename(self.filename.strpath)[len(self._cwd) :]
relative_filename = self.filename.strpath[len(self._cwd) :]
assert not relative_filename.startswith(("/", "\\"))
return relative_filename
@ -110,14 +110,14 @@ class Program(Target):
if self._cwd:
return f"program (relative) {json.repr(self._cwd)} / {json.repr(self._get_relative_program())}"
else:
return f"program {json.repr(compat.filename(self.filename.strpath))}"
return f"program {json.repr(self.filename.strpath)}"
def configure(self, session):
if self._cwd:
session.config["cwd"] = self._cwd
session.config["program"] = self._get_relative_program()
else:
session.config["program"] = compat.filename(self.filename.strpath)
session.config["program"] = self.filename.strpath
session.config["args"] = self.args

View file

@ -26,8 +26,7 @@ def cli(pyfile):
os.write(1, pickle.dumps(exc))
sys.exit(1)
# Check that sys.argv has the correct type after parsing - there should be
# no bytes on Python 3, nor unicode on Python 2.
# Check that sys.argv has the correct type after parsing - there should be no bytes.
assert all(isinstance(s, str) for s in sys.argv)
# We only care about options that correspond to public switches.

View file

@ -4,7 +4,6 @@
import pytest
from debugpy.common import compat
from tests import code, debug, log, net, test_data
from tests.debug import runners, targets
from tests.patterns import some
@ -50,7 +49,7 @@ def test_django_breakpoint_no_multiproc(start_django, bp_target):
"code": (paths.app_py, lines.app_py["bphome"], "home"),
"template": (paths.hello_html, 8, "Django Template"),
}[bp_target]
bp_var_content = compat.force_str("Django-Django-Test")
bp_var_content = "Django-Django-Test"
with debug.Session() as session:
with start_django(session):
@ -79,7 +78,7 @@ def test_django_breakpoint_no_multiproc(start_django, bp_target):
{
"name": "content",
"type": "str",
"value": compat.unicode_repr(bp_var_content),
"value": repr(bp_var_content),
"presentationHint": {"attributes": ["rawString"]},
"evaluateName": "content",
"variablesReference": 0,
@ -188,7 +187,7 @@ def test_django_exception_no_multiproc(start_django, exc_type):
def test_django_breakpoint_multiproc(start_django):
bp_line = lines.app_py["bphome"]
bp_var_content = compat.force_str("Django-Django-Test")
bp_var_content = "Django-Django-Test"
with debug.Session() as parent_session:
with start_django(parent_session, multiprocess=True):
@ -214,7 +213,7 @@ def test_django_breakpoint_multiproc(start_django):
{
"name": "content",
"type": "str",
"value": compat.unicode_repr(bp_var_content),
"value": repr(bp_var_content),
"presentationHint": {"attributes": ["rawString"]},
"evaluateName": "content",
}

View file

@ -275,15 +275,12 @@ def test_return_values(pyfile, target, run, ret_vis):
# On Python 3, variable names can contain Unicode characters.
# On Python 2, they must be ASCII, but using a Unicode character in an expression should not crash debugger.
def test_unicode(pyfile, target, run):
@pyfile
def code_to_debug():
import debuggee
import debugpy
# Since Unicode variable name is a SyntaxError at parse time in Python 2,
# this needs to do a roundabout way of setting it to avoid parse issues.
globals()["\u16A0"] = 123
debuggee.setup()
debugpy.breakpoint()
@ -634,8 +631,8 @@ def test_evaluate_thread_locks(pyfile, target, run):
"""
import debuggee
import queue
import threading
from debugpy.common.compat import queue
debuggee.setup()

View file

@ -5,7 +5,6 @@
import pytest
import sys
from debugpy.common import compat
from tests import code, debug, log, net, test_data
from tests.debug import runners, targets
from tests.patterns import some
@ -73,7 +72,7 @@ def test_flask_breakpoint_no_multiproc(start_flask, bp_target):
"code": (paths.app_py, lines.app_py["bphome"], "home"),
"template": (paths.hello_html, 8, "template"),
}[bp_target]
bp_var_content = compat.force_str("Flask-Jinja-Test")
bp_var_content = "Flask-Jinja-Test"
with debug.Session() as session:
with start_flask(session):
@ -217,7 +216,7 @@ def test_flask_exception_no_multiproc(start_flask, exc_type):
def test_flask_breakpoint_multiproc(start_flask):
bp_line = lines.app_py["bphome"]
bp_var_content = compat.force_str("Flask-Jinja-Test")
bp_var_content = "Flask-Jinja-Test"
with debug.Session() as parent_session:
with start_flask(parent_session, multiprocess=True):

View file

@ -65,9 +65,9 @@ def test_log_dir_env(pyfile, tmpdir, run, target):
with check_logs(tmpdir, run, pydevd_log=True):
with debug.Session() as session:
session.log_dir = None
session.spawn_adapter.env["DEBUGPY_LOG_DIR"] = tmpdir
session.spawn_adapter.env["DEBUGPY_LOG_DIR"] = tmpdir.strpath
if run.request != "launch":
session.spawn_debuggee.env["DEBUGPY_LOG_DIR"] = tmpdir
session.spawn_debuggee.env["DEBUGPY_LOG_DIR"] = tmpdir.strpath
backchannel = session.open_backchannel()
with run(session, target(code_to_debug)):

View file

@ -330,15 +330,12 @@ def test_frame_eval(pyfile, target, run, frame_eval):
@pytest.mark.parametrize("run", [runners.all_launch[0]])
def test_unicode_dir(tmpdir, run, target):
from debugpy.common import compat
unicode_chars = "á"
directory = os.path.join(compat.force_unicode(str(tmpdir), "ascii"), unicode_chars)
directory = compat.filename_str(directory)
directory = os.path.join(str(tmpdir), unicode_chars)
os.makedirs(directory)
code_to_debug = os.path.join(directory, compat.filename_str("experiment.py"))
code_to_debug = os.path.join(directory, "experiment.py")
with open(code_to_debug, "wb") as stream:
stream.write(
b"""
@ -352,7 +349,7 @@ backchannel.send('ok')
with debug.Session() as session:
backchannel = session.open_backchannel()
with run(session, target(compat.filename_str(code_to_debug))):
with run(session, target(code_to_debug)):
pass
received = backchannel.receive()

View file

@ -12,7 +12,7 @@ import socket
import threading
import time
from debugpy.common import compat, log
from debugpy.common import log, util
from tests.patterns import some
@ -29,7 +29,7 @@ def get_test_server_port(start, stop):
"""
try:
worker_id = compat.force_ascii(os.environ["PYTEST_XDIST_WORKER"])
worker_id = util.force_ascii(os.environ["PYTEST_XDIST_WORKER"])
except KeyError:
n = 0
else:
@ -56,7 +56,7 @@ def wait_until_port_is_listening(port, interval=1, max_attempts=1000):
Connection is immediately closed before returning.
"""
for i in compat.xrange(1, max_attempts + 1):
for i in range(1, max_attempts + 1):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
log.info("Probing localhost:{0} (attempt {1})...", port, i)

View file

@ -12,8 +12,7 @@ import py.path
import re
import sys
from debugpy.common import compat
from debugpy.common.compat import unicode, xrange
from debugpy.common import util
import pydevd_file_utils
@ -166,26 +165,18 @@ class Path(Some):
"""Matches any string that matches the specified path.
Uses os.path.normcase() to normalize both strings before comparison.
If one string is unicode, but the other one is not, both strings are normalized
to unicode using sys.getfilesystemencoding().
"""
def __init__(self, path):
if isinstance(path, py.path.local):
path = path.strpath
if isinstance(path, bytes):
path = path.encode(sys.getfilesystemencoding())
assert isinstance(path, unicode)
assert isinstance(path, str)
self.path = path
def __repr__(self):
return "path({self.path!r})"
def __str__(self):
return compat.filename_str(self.path)
def __unicode__(self):
return self.path
def __getstate__(self):
@ -195,7 +186,7 @@ class Path(Some):
if isinstance(other, py.path.local):
other = other.strpath
if isinstance(other, unicode):
if isinstance(other, str):
pass
elif isinstance(other, bytes):
other = other.encode(sys.getfilesystemencoding())
@ -242,8 +233,8 @@ class ListContaining(Some):
# tuples of equal length with items - i.e. all potential subsequences. So,
# given other=[1, 2, 3, 4, 5] and items=(2, 3, 4), we want to get a list
# like [(1, 2, 3), (2, 3, 4), (3, 4, 5)] - and then search for items in it.
iters = [itertools.islice(other, i, None) for i in xrange(0, len(items))]
subseqs = compat.izip(*iters)
iters = [itertools.islice(other, i, None) for i in range(0, len(items))]
subseqs = zip(*iters)
return any(subseq == items for subseq in subseqs)
@ -302,7 +293,7 @@ class SuchThat(Also):
try:
return self.name
except AttributeError:
return f"({self.pattern!r} if {compat.nameof(self.condition)})"
return f"({self.pattern!r} if {util.nameof(self.condition)})"
def _also(self, value):
return self.condition(value)
@ -341,9 +332,6 @@ class EqualTo(Also):
def __str__(self):
return str(self.obj)
def __unicode__(self):
return unicode(self.obj)
def __getstate__(self):
return self.obj
@ -386,7 +374,7 @@ class Matching(Also):
"""
def __init__(self, pattern, regex, flags=0):
assert isinstance(regex, bytes) or isinstance(regex, unicode)
assert isinstance(regex, bytes) or isinstance(regex, str)
super(Matching, self).__init__(pattern)
self.regex = regex
self.flags = flags
@ -407,8 +395,8 @@ class Matching(Also):
if not isinstance(value, bytes):
return NotImplemented
regex += b"$"
elif isinstance(regex, unicode):
if not isinstance(value, unicode):
elif isinstance(regex, str):
if not isinstance(value, str):
return NotImplemented
regex += "$"
else:

View file

@ -7,7 +7,6 @@
import py.path
from debugpy.common.compat import unicode
from tests import code
from tests.patterns import some, _impl
@ -33,14 +32,14 @@ def frame(source, line, **kwargs):
If source is py.path.local, it's automatically wrapped with some.dap.source().
If line is unicode, it is treated as a line marker, and translated to a line
If line is str, it is treated as a line marker, and translated to a line
number via get_marked_line_numbers(source["path"]) if possible.
"""
if isinstance(source, py.path.local):
source = some.dap.source(source)
if isinstance(line, unicode):
if isinstance(line, str):
if isinstance(source, dict):
path = source["path"]
elif isinstance(source, _impl.DictContaining):

View file

@ -79,10 +79,10 @@ __all__ = [
"tuple",
]
import builtins
import numbers
import re
from debugpy.common.compat import builtins
from tests.patterns import _impl

View file

@ -10,7 +10,7 @@ import sys
import threading
import types
from debugpy.common import compat, log, timestamp
from debugpy.common import log, timestamp
import tests
from tests import code, logs
from tests.debug import runners, session, targets
@ -151,7 +151,7 @@ else:
def long_tmpdir(request, tmpdir):
"""Like tmpdir, but ensures that it's a long rather than short filename on Win32.
"""
path = compat.filename(tmpdir.strpath)
path = tmpdir.strpath
buffer = ctypes.create_unicode_buffer(512)
if GetLongPathNameW(path, buffer, len(buffer)):
path = buffer.value
@ -222,7 +222,7 @@ def pyfile(request, long_tmpdir):
# Write it to file.
tmpfile = long_tmpdir / (name + ".py")
tmpfile.strpath = compat.filename(tmpfile.strpath)
tmpfile.strpath = tmpfile.strpath
assert not tmpfile.check()
tmpfile.write(source)

View file

@ -5,10 +5,10 @@
import collections
import contextlib
import itertools
import queue
import threading
from debugpy.common import compat, json, log, messaging, timestamp
from debugpy.common.compat import queue
from debugpy.common import json, log, messaging, timestamp
from tests.patterns import some
@ -831,7 +831,7 @@ def Response(request, body=some.object):
elif body is some.error or body == some.error:
items += (("success", False),)
if body == some.error:
items += (("message", compat.force_str(body)),)
items += (("message", str(body)),)
else:
items += (("body", body),)