From 0f428178b06b0795146e9fca2dfda579fd5f8bdc Mon Sep 17 00:00:00 2001 From: Pavel Minaev Date: Tue, 12 Apr 2022 18:30:48 -0700 Subject: [PATCH] Remove debugpy.compat, custom !j formatter for JSON, and related helpers. --- src/debugpy/__init__.py | 4 +- src/debugpy/adapter/__main__.py | 4 +- src/debugpy/adapter/clients.py | 31 +++-- src/debugpy/adapter/launchers.py | 6 +- src/debugpy/adapter/servers.py | 4 +- src/debugpy/common/compat.py | 210 ------------------------------- src/debugpy/common/json.py | 10 +- src/debugpy/common/log.py | 6 +- src/debugpy/common/messaging.py | 70 +++++------ src/debugpy/common/util.py | 102 +++++++++++++-- src/debugpy/launcher/debuggee.py | 4 +- src/debugpy/launcher/handlers.py | 21 ++-- src/debugpy/server/api.py | 4 +- src/debugpy/server/cli.py | 28 ++--- tests/code.py | 6 +- tests/debug/output.py | 8 +- tests/debug/runners.py | 6 +- tests/debug/session.py | 26 ++-- tests/debug/targets.py | 8 +- tests/debugpy/server/test_cli.py | 3 +- tests/debugpy/test_django.py | 9 +- tests/debugpy/test_evaluate.py | 5 +- tests/debugpy/test_flask.py | 5 +- tests/debugpy/test_log.py | 4 +- tests/debugpy/test_run.py | 9 +- tests/net.py | 6 +- tests/patterns/_impl.py | 30 ++--- tests/patterns/dap.py | 5 +- tests/patterns/some.py | 2 +- tests/pytest_fixtures.py | 6 +- tests/timeline.py | 6 +- 31 files changed, 240 insertions(+), 408 deletions(-) delete mode 100644 src/debugpy/common/compat.py diff --git a/src/debugpy/__init__.py b/src/debugpy/__init__.py index 350adce5..8e0012b0 100644 --- a/src/debugpy/__init__.py +++ b/src/debugpy/__init__.py @@ -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. diff --git a/src/debugpy/adapter/__main__.py b/src/debugpy/adapter/__main__.py index e18652c9..7eaa1898 100644 --- a/src/debugpy/adapter/__main__.py +++ b/src/debugpy/adapter/__main__.py @@ -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: diff --git a/src/debugpy/adapter/clients.py b/src/debugpy/adapter/clients.py index d4476391..b2977a79 100644 --- a/src/debugpy/adapter/clients.py +++ b/src/debugpy/adapter/clients.py @@ -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 diff --git a/src/debugpy/adapter/launchers.py b/src/debugpy/adapter/launchers.py index 6a3bc1a4..f71502f1 100644 --- a/src/debugpy/adapter/launchers.py +++ b/src/debugpy/adapter/launchers.py @@ -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 diff --git a/src/debugpy/adapter/servers.py b/src/debugpy/adapter/servers.py index 9cf59467..b3b2c376 100644 --- a/src/debugpy/adapter/servers.py +++ b/src/debugpy/adapter/servers.py @@ -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), ] diff --git a/src/debugpy/common/compat.py b/src/debugpy/common/compat.py deleted file mode 100644 index 24802007..00000000 --- a/src/debugpy/common/compat.py +++ /dev/null @@ -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 "" - 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() diff --git a/src/debugpy/common/json.py b/src/debugpy/common/json.py index 0b794c1e..7eb74fe8 100644 --- a/src/debugpy/common/json.py +++ b/src/debugpy/common/json.py @@ -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) diff --git a/src/debugpy/common/log.py b/src/debugpy/common/log.py index 1a00a37d..17135c39 100644 --- a/src/debugpy/common/log.py +++ b/src/debugpy/common/log.py @@ -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)") diff --git a/src/debugpy/common/messaging.py b/src/debugpy/common/messaging.py index c0b9b5aa..bb344477 100644 --- a/src/debugpy/common/messaging.py +++ b/src/debugpy/common/messaging.py @@ -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, ) diff --git a/src/debugpy/common/util.py b/src/debugpy/common/util.py index 42332970..e719d29b 100644 --- a/src/debugpy/common/util.py +++ b/src/debugpy/common/util.py @@ -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 "" + 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 diff --git a/src/debugpy/launcher/debuggee.py b/src/debugpy/launcher/debuggee.py index 57d06901..4e03eefa 100644 --- a/src/debugpy/launcher/debuggee.py +++ b/src/debugpy/launcher/debuggee.py @@ -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, }, ) diff --git a/src/debugpy/launcher/handlers.py b/src/debugpy/launcher/handlers.py index 2c38a033..15b8079d 100644 --- a/src/debugpy/launcher/handlers.py +++ b/src/debugpy/launcher/handlers.py @@ -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 diff --git a/src/debugpy/server/api.py b/src/debugpy/server/api.py index 3a98b2e0..207278da 100644 --- a/src/debugpy/server/api.py +++ b/src/debugpy/server/api.py @@ -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) diff --git a/src/debugpy/server/cli.py b/src/debugpy/server/cli.py index b36c33d5..048ce0b3 100644 --- a/src/debugpy/server/cli.py +++ b/src/debugpy/server/cli.py @@ -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(): diff --git a/tests/code.py b/tests/code.py index 9adb6695..0c94df7e 100644 --- a/tests/code.py +++ b/tests/code.py @@ -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 diff --git a/tests/debug/output.py b/tests/debug/output.py index a19a3af9..a9970bd9 100644 --- a/tests/debug/output.py +++ b/tests/debug/output.py @@ -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) diff --git a/tests/debug/runners.py b/tests/debug/runners.py index d2a8e81f..638ff109 100644 --- a/tests/debug/runners.py +++ b/tests/debug/runners.py @@ -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] diff --git a/tests/debug/session.py b/tests/debug/session.py index 250e054a..d17eb5a7 100644 --- a/tests/debug/session.py +++ b/tests/debug/session.py @@ -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", diff --git a/tests/debug/targets.py b/tests/debug/targets.py index e6fb4714..fc67084a 100644 --- a/tests/debug/targets.py +++ b/tests/debug/targets.py @@ -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 diff --git a/tests/debugpy/server/test_cli.py b/tests/debugpy/server/test_cli.py index da78ddf9..6b86c6b1 100644 --- a/tests/debugpy/server/test_cli.py +++ b/tests/debugpy/server/test_cli.py @@ -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. diff --git a/tests/debugpy/test_django.py b/tests/debugpy/test_django.py index 6758819b..70697c3b 100644 --- a/tests/debugpy/test_django.py +++ b/tests/debugpy/test_django.py @@ -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", } diff --git a/tests/debugpy/test_evaluate.py b/tests/debugpy/test_evaluate.py index 532a1782..7c9193bd 100644 --- a/tests/debugpy/test_evaluate.py +++ b/tests/debugpy/test_evaluate.py @@ -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() diff --git a/tests/debugpy/test_flask.py b/tests/debugpy/test_flask.py index 4217b02a..4db245bb 100644 --- a/tests/debugpy/test_flask.py +++ b/tests/debugpy/test_flask.py @@ -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): diff --git a/tests/debugpy/test_log.py b/tests/debugpy/test_log.py index 7429773d..afe07597 100644 --- a/tests/debugpy/test_log.py +++ b/tests/debugpy/test_log.py @@ -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)): diff --git a/tests/debugpy/test_run.py b/tests/debugpy/test_run.py index 53876283..280bb5ce 100644 --- a/tests/debugpy/test_run.py +++ b/tests/debugpy/test_run.py @@ -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() diff --git a/tests/net.py b/tests/net.py index 26265ff2..301c2772 100644 --- a/tests/net.py +++ b/tests/net.py @@ -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) diff --git a/tests/patterns/_impl.py b/tests/patterns/_impl.py index 6f3af210..49bcf1e2 100644 --- a/tests/patterns/_impl.py +++ b/tests/patterns/_impl.py @@ -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: diff --git a/tests/patterns/dap.py b/tests/patterns/dap.py index a06e9c69..3d95a946 100644 --- a/tests/patterns/dap.py +++ b/tests/patterns/dap.py @@ -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): diff --git a/tests/patterns/some.py b/tests/patterns/some.py index 62f3f95a..92dcdba1 100644 --- a/tests/patterns/some.py +++ b/tests/patterns/some.py @@ -79,10 +79,10 @@ __all__ = [ "tuple", ] +import builtins import numbers import re -from debugpy.common.compat import builtins from tests.patterns import _impl diff --git a/tests/pytest_fixtures.py b/tests/pytest_fixtures.py index ad1f4c24..2f727105 100644 --- a/tests/pytest_fixtures.py +++ b/tests/pytest_fixtures.py @@ -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) diff --git a/tests/timeline.py b/tests/timeline.py index 41990642..69445d11 100644 --- a/tests/timeline.py +++ b/tests/timeline.py @@ -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),)