mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
366 lines
11 KiB
Python
366 lines
11 KiB
Python
from debugger_protocol._base import Readonly, WithRepr
|
|
from debugger_protocol.arg import param_from_datatype
|
|
from . import MESSAGE_TYPES, Message
|
|
|
|
"""
|
|
From the schema:
|
|
|
|
MESSAGE = [
|
|
name
|
|
base
|
|
description
|
|
props: [PROPERTY + (properties: [PROPERTY])]
|
|
]
|
|
|
|
PROPERTY = [
|
|
name
|
|
type: choices (one or a list)
|
|
(enum/_enum)
|
|
description
|
|
required: True/False (default: False)
|
|
]
|
|
|
|
inheritance: override properties of base
|
|
"""
|
|
|
|
|
|
class ProtocolMessage(Readonly, WithRepr, Message):
|
|
"""Base class of requests, responses, and events."""
|
|
|
|
_reqid = 0
|
|
TYPE = None
|
|
|
|
@classmethod
|
|
def from_data(cls, type, seq, **kwargs):
|
|
"""Return an instance based on the given raw data."""
|
|
return cls(type=type, seq=seq, **kwargs)
|
|
|
|
@classmethod
|
|
def _next_reqid(cls):
|
|
reqid = ProtocolMessage._reqid
|
|
ProtocolMessage._reqid += 1
|
|
return reqid
|
|
|
|
_NOT_SET = object()
|
|
|
|
def __init__(self, seq=_NOT_SET, **kwargs):
|
|
type = kwargs.pop('type', self.TYPE)
|
|
if seq is self._NOT_SET:
|
|
seq = self._next_reqid()
|
|
self._bind_attrs(
|
|
type=type or None,
|
|
seq=int(seq) if seq or seq == 0 else None,
|
|
)
|
|
self._validate()
|
|
|
|
def _validate(self):
|
|
if self.type is None:
|
|
raise TypeError('missing type')
|
|
elif self.TYPE is not None and self.type != self.TYPE:
|
|
raise ValueError('type must be {!r}'.format(self.TYPE))
|
|
elif self.type not in MESSAGE_TYPES:
|
|
raise ValueError('unsupported type {!r}'.format(self.type))
|
|
|
|
if self.seq is None:
|
|
raise TypeError('missing seq')
|
|
elif self.seq < 0:
|
|
msg = '"seq" must be a non-negative int, got {!r}'
|
|
raise ValueError(msg.format(self.seq))
|
|
|
|
def _init_args(self):
|
|
if self.TYPE is None:
|
|
yield ('type', self.type)
|
|
yield ('seq', self.seq)
|
|
|
|
def as_data(self):
|
|
"""Return serializable data for the instance."""
|
|
data = {
|
|
'type': self.type,
|
|
'seq': self.seq,
|
|
}
|
|
return data
|
|
|
|
|
|
##################################
|
|
|
|
class Request(ProtocolMessage):
|
|
"""A client or server-initiated request."""
|
|
|
|
TYPE = 'request'
|
|
TYPE_KEY = 'command'
|
|
|
|
COMMAND = None
|
|
ARGUMENTS = None
|
|
ARGUMENTS_REQUIRED = None
|
|
|
|
@classmethod
|
|
def from_data(cls, type, seq, command, arguments=None):
|
|
"""Return an instance based on the given raw data."""
|
|
return super(Request, cls).from_data(
|
|
type, seq,
|
|
command=command,
|
|
arguments=arguments,
|
|
)
|
|
|
|
@classmethod
|
|
def _arguments_required(cls):
|
|
if cls.ARGUMENTS_REQUIRED is None:
|
|
return cls.ARGUMENTS is not None
|
|
return cls.ARGUMENTS_REQUIRED
|
|
|
|
def __init__(self, arguments=None, **kwargs):
|
|
command = kwargs.pop('command', self.COMMAND)
|
|
args = None
|
|
if arguments is not None:
|
|
try:
|
|
arguments = dict(arguments)
|
|
except TypeError:
|
|
pass
|
|
if self.ARGUMENTS is not None:
|
|
param = param_from_datatype(self.ARGUMENTS)
|
|
args = param.bind(arguments)
|
|
if args is None:
|
|
raise TypeError('bad arguments {!r}'.format(arguments))
|
|
arguments = args.coerce()
|
|
self._bind_attrs(
|
|
command=command or None,
|
|
arguments=arguments or None,
|
|
_args=args,
|
|
)
|
|
super(Request, self).__init__(**kwargs)
|
|
|
|
def _validate(self):
|
|
super(Request, self)._validate()
|
|
|
|
if self.command is None:
|
|
raise TypeError('missing command')
|
|
elif self.COMMAND is not None and self.command != self.COMMAND:
|
|
raise ValueError('command must be {!r}'.format(self.COMMAND))
|
|
|
|
if self.arguments is None:
|
|
if self._arguments_required():
|
|
raise TypeError('missing arguments')
|
|
else:
|
|
if self.ARGUMENTS is None:
|
|
raise TypeError('got unexpected arguments')
|
|
self._args.validate()
|
|
|
|
def _init_args(self):
|
|
if self.COMMAND is None:
|
|
yield ('command', self.command)
|
|
if self.arguments is not None:
|
|
yield ('arguments', self.arguments)
|
|
yield ('seq', self.seq)
|
|
|
|
def as_data(self):
|
|
"""Return serializable data for the instance."""
|
|
data = super(Request, self).as_data()
|
|
data.update({
|
|
'command': self.command,
|
|
})
|
|
if self.arguments is not None:
|
|
data.update({
|
|
'arguments': self.arguments.as_data(),
|
|
})
|
|
return data
|
|
|
|
|
|
class Response(ProtocolMessage):
|
|
"""Response to a request."""
|
|
|
|
TYPE = 'response'
|
|
TYPE_KEY = 'command'
|
|
|
|
COMMAND = None
|
|
BODY = None
|
|
ERROR_BODY = None
|
|
BODY_REQUIRED = None
|
|
ERROR_BODY_REQUIRED = None
|
|
|
|
@classmethod
|
|
def from_data(cls, type, seq, request_seq, command, success,
|
|
body=None, message=None):
|
|
"""Return an instance based on the given raw data."""
|
|
return super(Response, cls).from_data(
|
|
type, seq,
|
|
request_seq=request_seq,
|
|
command=command,
|
|
success=success,
|
|
body=body,
|
|
message=message,
|
|
)
|
|
|
|
@classmethod
|
|
def _body_required(cls, success=True):
|
|
required = cls.BODY_REQUIRED if success else cls.ERROR_BODY_REQUIRED
|
|
if required is not None:
|
|
return required
|
|
bodyclass = cls.BODY if success else cls.ERROR_BODY
|
|
return bodyclass is not None
|
|
|
|
def __init__(self, request_seq, body=None, message=None, success=True,
|
|
**kwargs):
|
|
command = kwargs.pop('command', self.COMMAND)
|
|
reqseq = request_seq
|
|
bodyarg = None
|
|
if body is not None:
|
|
try:
|
|
body = dict(body)
|
|
except TypeError:
|
|
pass
|
|
bodyclass = self.BODY if success else self.ERROR_BODY
|
|
if bodyclass is not None:
|
|
param = param_from_datatype(bodyclass)
|
|
bodyarg = param.bind(body)
|
|
if bodyarg is None:
|
|
raise TypeError('bad body type {!r}'.format(body))
|
|
body = bodyarg.coerce()
|
|
self._bind_attrs(
|
|
command=command or None,
|
|
request_seq=int(reqseq) if reqseq or reqseq == 0 else None,
|
|
body=body or None,
|
|
_bodyarg=bodyarg,
|
|
message=message or None,
|
|
success=bool(success),
|
|
)
|
|
super(Response, self).__init__(**kwargs)
|
|
|
|
def _validate(self):
|
|
super(Response, self)._validate()
|
|
|
|
if self.request_seq is None:
|
|
raise TypeError('missing request_seq')
|
|
elif self.request_seq < 0:
|
|
msg = 'request_seq must be a non-negative int, got {!r}'
|
|
raise ValueError(msg.format(self.request_seq))
|
|
|
|
if not self.command:
|
|
raise TypeError('missing command')
|
|
elif self.COMMAND is not None and self.command != self.COMMAND:
|
|
raise ValueError('command must be {!r}'.format(self.COMMAND))
|
|
|
|
if self.body is None:
|
|
if self._body_required(self.success):
|
|
raise TypeError('missing body')
|
|
elif self._bodyarg is None:
|
|
raise ValueError('got unexpected body')
|
|
else:
|
|
self._bodyarg.validate()
|
|
|
|
if not self.success and not self.message:
|
|
raise TypeError('missing message')
|
|
|
|
def _init_args(self):
|
|
if self.COMMAND is None:
|
|
yield ('command', self.command)
|
|
yield ('request_seq', self.request_seq)
|
|
yield ('success', self.success)
|
|
if not self.success:
|
|
yield ('message', self.message)
|
|
if self.body is not None:
|
|
yield ('body', self.body)
|
|
yield ('seq', self.seq)
|
|
|
|
def as_data(self):
|
|
"""Return serializable data for the instance."""
|
|
data = super(Response, self).as_data()
|
|
data.update({
|
|
'request_seq': self.request_seq,
|
|
'command': self.command,
|
|
'success': self.success,
|
|
})
|
|
if self.body is not None:
|
|
data.update({
|
|
'body': self.body.as_data(),
|
|
})
|
|
if self.message is not None:
|
|
data.update({
|
|
'message': self.message,
|
|
})
|
|
return data
|
|
|
|
|
|
##################################
|
|
|
|
class Event(ProtocolMessage):
|
|
"""Server-initiated event."""
|
|
|
|
TYPE = 'event'
|
|
TYPE_KEY = 'event'
|
|
|
|
EVENT = None
|
|
BODY = None
|
|
BODY_REQUIRED = None
|
|
|
|
@classmethod
|
|
def from_data(cls, type, seq, event, body=None):
|
|
"""Return an instance based on the given raw data."""
|
|
return super(Event, cls).from_data(type, seq, event=event, body=body)
|
|
|
|
@classmethod
|
|
def _body_required(cls):
|
|
if cls.BODY_REQUIRED is None:
|
|
return cls.BODY is not None
|
|
return cls.BODY_REQUIRED
|
|
|
|
def __init__(self, body=None, **kwargs):
|
|
event = kwargs.pop('event', self.EVENT)
|
|
bodyarg = None
|
|
if body is not None:
|
|
try:
|
|
body = dict(body)
|
|
except TypeError:
|
|
pass
|
|
if self.BODY is not None:
|
|
param = param_from_datatype(self.BODY)
|
|
bodyarg = param.bind(body)
|
|
if bodyarg is None:
|
|
raise TypeError('bad body type {!r}'.format(body))
|
|
body = bodyarg.coerce()
|
|
|
|
self._bind_attrs(
|
|
event=event or None,
|
|
body=body or None,
|
|
_bodyarg=bodyarg,
|
|
)
|
|
super(Event, self).__init__(**kwargs)
|
|
|
|
def _validate(self):
|
|
super(Event, self)._validate()
|
|
|
|
if self.event is None:
|
|
raise TypeError('missing event')
|
|
if self.EVENT is not None and self.event != self.EVENT:
|
|
msg = 'event must be {!r}, got {!r}'
|
|
raise ValueError(msg.format(self.EVENT, self.event))
|
|
|
|
if self.body is None:
|
|
if self._body_required():
|
|
raise TypeError('missing body')
|
|
elif self._bodyarg is None:
|
|
raise ValueError('got unexpected body')
|
|
else:
|
|
self._bodyarg.validate()
|
|
|
|
def _init_args(self):
|
|
if self.EVENT is None:
|
|
yield ('event', self.event)
|
|
if self.body is not None:
|
|
yield ('body', self.body)
|
|
yield ('seq', self.seq)
|
|
|
|
@property
|
|
def name(self):
|
|
return self.event
|
|
|
|
def as_data(self):
|
|
"""Return serializable data for the instance."""
|
|
data = super(Event, self).as_data()
|
|
data.update({
|
|
'event': self.event,
|
|
})
|
|
if self.body is not None:
|
|
data.update({
|
|
'body': self.body.as_data(),
|
|
})
|
|
return data
|