Finish typing debupy python code

This commit is contained in:
rchiodo 2024-07-05 22:59:42 +00:00
parent e528e2dbe3
commit 622e3c6967
8 changed files with 75 additions and 55 deletions

View file

@ -8,6 +8,7 @@ import codecs
import locale
import os
import sys
from typing import Any
# WARNING: debugpy and submodules must not be imported on top level in this module,
# and should be imported locally inside main() instead.
@ -53,7 +54,7 @@ def main(args):
if args.for_server is None:
adapter.access_token = codecs.encode(os.urandom(32), "hex").decode("ascii")
endpoints = {}
endpoints: dict[str, Any] = {}
try:
client_host, client_port = clients.serve(args.host, args.port)
except Exception as exc:

View file

@ -6,7 +6,9 @@ from __future__ import annotations
import atexit
import os
import socket
import sys
from typing import Literal, Union
import debugpy
from debugpy import adapter, common, launcher
@ -41,7 +43,7 @@ class Client(components.Component):
"pathFormat": json.enum("path", optional=True), # we don't support "uri"
}
def __init__(self, sock):
def __init__(self, sock: Union[Literal["stdio"], socket.socket]):
if sock == "stdio":
log.info("Connecting to client over stdio...", self)
self.using_stdio = True
@ -67,7 +69,7 @@ class Client(components.Component):
fully handled.
"""
self.start_request = None
self.start_request: Union[messaging.Request, None] = None
"""The "launch" or "attach" request as received from the client.
"""
@ -124,11 +126,12 @@ class Client(components.Component):
self.client.channel.propagate(event)
def _propagate_deferred_events(self):
log.debug("Propagating deferred events to {0}...", self.client)
for event in self._deferred_events:
log.debug("Propagating deferred {0}", event.describe())
self.client.channel.propagate(event)
log.info("All deferred events propagated to {0}.", self.client)
if self._deferred_events is not None:
log.debug("Propagating deferred events to {0}...", self.client)
for event in self._deferred_events:
log.debug("Propagating deferred {0}", event.describe())
self.client.channel.propagate(event)
log.info("All deferred events propagated to {0}.", self.client)
self._deferred_events = None
# Generic event handler. There are no specific handlers for client events, because
@ -202,9 +205,10 @@ class Client(components.Component):
#
# See https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522
# for the sequence of request and events necessary to orchestrate the start.
@staticmethod
def _start_message_handler(f):
@components.Component.message_handler
def handle(self, request):
def handle(self, request: messaging.Message):
assert request.is_request("launch", "attach")
if self._initialize_request is None:
raise request.isnt_valid("Session is not initialized yet")
@ -215,15 +219,16 @@ class Client(components.Component):
if self.session.no_debug:
servers.dont_wait_for_first_connection()
request_options = request("debugOptions", json.array(str))
self.session.debug_options = debug_options = set(
request("debugOptions", json.array(str))
)
f(self, request)
if request.response is not None:
if isinstance(request, messaging.Request) and request.response is not None:
return
if self.server:
if self.server and isinstance(request, messaging.Request):
self.server.initialize(self._initialize_request)
self._initialize_request = None
@ -267,7 +272,7 @@ class Client(components.Component):
except messaging.MessageHandlingError as exc:
exc.propagate(request)
if self.session.no_debug:
if self.session.no_debug and isinstance(request, messaging.Request):
self.start_request = request
self.has_started = True
request.respond({})
@ -335,6 +340,7 @@ class Client(components.Component):
launcher_python = python[0]
program = module = code = ()
args = []
if "program" in request:
program = request("program", str)
args = [program]
@ -391,7 +397,7 @@ class Client(components.Component):
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.
cwd = None if program == () else (os.path.dirname(program) or None)
cwd = None if program == () else (os.path.dirname(str(program)) or None)
sudo = bool(property_or_debug_option("sudo", "Sudo"))
if sudo and sys.platform == "win32":
@ -484,7 +490,7 @@ class Client(components.Component):
else:
if not servers.is_serving():
servers.serve()
host, port = servers.listener.getsockname()
host, port = servers.listener.getsockname() if servers.listener is not None else ("", 0)
# There are four distinct possibilities here.
#
@ -576,9 +582,9 @@ class Client(components.Component):
request.cant_handle("{0} is already being debugged.", conn)
@message_handler
def configurationDone_request(self, request):
def configurationDone_request(self, request: messaging.Request):
if self.start_request is None or self.has_started:
request.cant_handle(
raise request.cant_handle(
'"configurationDone" is only allowed during handling of a "launch" '
'or an "attach" request'
)
@ -623,7 +629,8 @@ class Client(components.Component):
def handle_response(response):
request.respond(response.body)
propagated_request.on_response(handle_response)
if propagated_request is not None:
propagated_request.on_response(handle_response)
return messaging.NO_RESPONSE
@ -649,7 +656,7 @@ class Client(components.Component):
result = {"debugpy": {"version": debugpy.__version__}}
if self.server:
try:
pydevd_info = self.server.channel.request("pydevdSystemInfo")
pydevd_info: messaging.AssociatableMessageDict = self.server.channel.request("pydevdSystemInfo")
except Exception:
# If the server has already disconnected, or couldn't handle it,
# report what we've got.
@ -754,7 +761,7 @@ class Client(components.Component):
if "host" not in body["connect"]:
body["connect"]["host"] = host if host is not None else "127.0.0.1"
if "port" not in body["connect"]:
if port is None:
if port is None and listener is not None:
_, port = listener.getsockname()
body["connect"]["port"] = port
@ -770,7 +777,7 @@ class Client(components.Component):
def serve(host, port):
global listener
listener = sockets.serve("Client", Client, host, port)
listener = sockets.serve("Client", Client, host, port) # type: ignore
sessions.report_sockets()
return listener.getsockname()

View file

@ -3,7 +3,7 @@
# for license information.
import functools
from typing import Union
from typing import Type, TypeVar, Union, cast
from debugpy.adapter.sessions import Session
from debugpy.common import json, log, messaging, util
@ -33,7 +33,7 @@ class Component(util.Observable):
to wait_for() a change caused by another component.
"""
def __init__(self, session: Session, stream: Union[messaging.JsonIOStream, None]=None, channel=None):
def __init__(self, session: Session, stream: Union[messaging.JsonIOStream, None]=None, channel: Union[messaging.JsonMessageChannel, None]=None):
assert (stream is None) ^ (channel is None)
try:
@ -53,6 +53,7 @@ class Component(util.Observable):
elif channel is not None:
channel.name = channel.stream.name = str(self)
channel.handlers = self
assert channel is not None
self.channel = channel
self.is_connected = True
@ -110,8 +111,9 @@ class Component(util.Observable):
self.is_connected = False
self.session.finalize("{0} has disconnected".format(self))
T = TypeVar('T')
def missing(session, type):
def missing(session, type: Type[T]) -> T:
class Missing(object):
"""A dummy component that raises ComponentNotAvailable whenever some
attribute is accessed on it.
@ -126,7 +128,7 @@ def missing(session, type):
except Exception as exc:
log.reraise_exception("{0} in {1}", exc, session)
return Missing()
return cast(type, Missing())
class Capabilities(dict):

View file

@ -292,7 +292,7 @@ class Server(components.Component):
# Generic request handler, used if there's no specific handler below.
@message_handler
def request(self, request):
def request(self, request: messaging.Message):
# Do not delegate requests from the server by default. There is a security
# boundary between the server and the adapter, and we cannot trust arbitrary
# requests sent over that boundary, since they may contain arbitrary code
@ -397,7 +397,7 @@ class Server(components.Component):
def serve(host="127.0.0.1", port=0):
global listener
listener = sockets.serve("Server", Connection, host, port)
listener = sockets.serve("Server", Connection, host, port) # type: ignore
sessions.report_sockets()
return listener.getsockname()
@ -422,21 +422,21 @@ def connections():
return list(_connections)
def wait_for_connection(session, predicate, timeout=None):
def wait_for_connection(session, predicate, timeout: Union[float, None]=None):
"""Waits until there is a server matching the specified predicate connected to
this adapter, and returns the corresponding Connection.
If there is more than one server connection already available, returns the oldest
one.
"""
def wait_for_timeout():
time.sleep(timeout)
wait_for_timeout.timed_out = True
if timeout is not None:
time.sleep(timeout)
setattr(wait_for_timeout, "timed_out", True)
with _lock:
_connections_changed.set()
wait_for_timeout.timed_out = timeout == 0
setattr(wait_for_timeout, "timed_out", timeout == 0)
if timeout:
thread = threading.Thread(
target=wait_for_timeout, name="servers.wait_for_connection() timeout"
@ -451,7 +451,7 @@ def wait_for_connection(session, predicate, timeout=None):
_connections_changed.clear()
conns = (conn for conn in _connections if predicate(conn))
conn = next(conns, None)
if conn is not None or wait_for_timeout.timed_out:
if conn is not None or getattr(wait_for_timeout, "timed_out") is True:
return conn
_connections_changed.wait()
@ -479,7 +479,7 @@ def dont_wait_for_first_connection():
def inject(pid, debugpy_args, on_output):
host, port = listener.getsockname()
host, port = listener.getsockname() if listener is not None else ("", 0)
cmdline = [
sys.executable,

View file

@ -7,7 +7,7 @@ import os
import signal
import threading
import time
from typing import Union
from typing import Union, cast
from debugpy import common
from debugpy.common import log, util
@ -182,7 +182,7 @@ class Session(util.Observable):
# can ask the launcher to kill it, do so instead of disconnecting
# from the server to prevent debuggee from running any more code.
self.launcher.terminate_debuggee()
else:
elif self.server.channel is not None:
# Otherwise, let the server handle it the best it can.
try:
self.server.channel.request(
@ -220,7 +220,8 @@ class Session(util.Observable):
self.wait_for(lambda: not self.launcher.is_connected)
try:
self.launcher.channel.close()
if self.launcher.channel is not None:
self.launcher.channel.close()
except Exception:
log.swallow_exception()
@ -232,7 +233,8 @@ class Session(util.Observable):
if self.client.restart_requested:
body["restart"] = True
try:
self.client.channel.send_event("terminated", body)
if self.client.channel is not None:
self.client.channel.send_event("terminated", body)
except Exception:
pass

View file

@ -9,6 +9,7 @@ import builtins
import json
import numbers
import operator
from typing import Any, Callable, Literal, Tuple, Union
JsonDecoder = json.JSONDecoder
@ -21,14 +22,14 @@ class JsonEncoder(json.JSONEncoder):
result is serialized instead of the object itself.
"""
def default(self, value):
def default(self, o):
try:
get_state = value.__getstate__
get_state = o.__getstate__
except AttributeError:
pass
else:
return get_state()
return super().default(value)
return super().default(o)
class JsonObject(object):
@ -93,12 +94,12 @@ class JsonObject(object):
# some substitutions - e.g. replacing () with some default value.
def _converter(value, classinfo):
def _converter(value: str, classinfo) -> Any:
"""Convert value (str) to number, otherwise return None if is not possible"""
for one_info in classinfo:
if issubclass(one_info, numbers.Number):
try:
return one_info(value)
return one_info(value) # pyright: ignore
except ValueError:
pass
@ -171,7 +172,7 @@ def enum(*values, **kwargs):
return validate
def array(validate_item=False, vectorize=False, size=None):
def array(validate_item: Union[Callable[..., Any], Literal[False]]=False, vectorize=False, size=None):
"""Returns a validator for a JSON array.
If the property is missing, it is treated as if it were []. Otherwise, it must
@ -213,11 +214,11 @@ def array(validate_item=False, vectorize=False, size=None):
)
elif isinstance(size, tuple):
assert 1 <= len(size) <= 2
size = tuple(operator.index(n) for n in size)
min_len, max_len = (size + (None,))[0:2]
sizes = tuple(operator.index(n) for n in size)
min_len, max_len = (sizes + (None,))[0:2]
validate_size = lambda value: (
"must have at least {0} elements".format(min_len)
if len(value) < min_len
if min_len is None or len(value) < min_len
else "must have at most {0} elements".format(max_len)
if max_len is not None and len(value) < max_len
else True
@ -250,7 +251,7 @@ def array(validate_item=False, vectorize=False, size=None):
return validate
def object(validate_value=False):
def object(validate_value: Union[Callable[..., Any], Tuple, Literal[False]]=False):
"""Returns a validator for a JSON object.
If the property is missing, it is treated as if it were {}. Otherwise, it must

View file

@ -344,7 +344,7 @@ class MessageDict(collections.OrderedDict):
such guarantee for outgoing messages.
"""
def __init__(self, message, items=None):
def __init__(self, message, items: Union[dict, None]=None):
assert message is None or isinstance(message, Message)
if items is None:
@ -683,7 +683,7 @@ class Request(Message):
arguments.associate_with(self)
self.arguments = arguments
self.response = None
self.response: Union[Response, None] = None
"""Response to this request.
For incoming requests, it is set as soon as the request handler returns.
@ -809,7 +809,7 @@ class OutgoingRequest(Request):
while self.response is None:
self.channel._handlers_enqueued.wait()
if raise_if_failed and not self.response.success and isinstance( self.response.body, Exception):
if raise_if_failed and not self.response.success and isinstance( self.response.body, BaseException):
raise self.response.body
return self.response.body
@ -1297,7 +1297,10 @@ class JsonMessageChannel(object):
def request(self, *args, **kwargs):
"""Same as send_request(...).wait_for_response()"""
return self.send_request(*args, **kwargs).wait_for_response()
# This should always raise an exception on failure
result = self.send_request(*args, **kwargs).wait_for_response()
assert not isinstance(result, BaseException)
return result
def propagate(self, message):
"""Sends a new message with the same type and payload.

View file

@ -86,12 +86,16 @@ class Singleton(object):
def __enter__(self):
"""Lock this singleton to prevent concurrent access."""
type(self)._lock.acquire()
lock = type(self)._lock
assert lock is not None
lock.acquire()
return self
def __exit__(self, exc_type, exc_value, exc_tb):
"""Unlock this singleton to allow concurrent access."""
type(self)._lock.release()
lock = type(self)._lock
assert lock is not None
lock.release()
def share(self):
"""Share this singleton, if it was originally created with shared=False."""
@ -138,7 +142,7 @@ class ThreadSafeSingleton(Singleton):
# ensure thread safety for the callers.
@staticmethod
def assert_locked(self):
def assert_locked(self): # type: ignore
lock = type(self)._lock
assert lock.acquire(blocking=False), (
"ThreadSafeSingleton accessed without locking. Either use with-statement, "