[debugger] PY-85315 add "view as image" hyperlink for variables in debugpy

Merge-request: IJ-MR-181659
Merged-by: Maxim Popov <maxim.popov@jetbrains.com>

GitOrigin-RevId: d44706f7aaa706ec7b4d368de1cea309428b47a5
This commit is contained in:
Maxim Popov
2026-01-12 23:19:50 +00:00
committed by intellij-monorepo-bot
parent 29940f9819
commit d2912a83a0
15 changed files with 552 additions and 74 deletions
@@ -70,6 +70,20 @@
"INSPECTIONS"
]
},
"parametersJson": {
"$ref": "#/definitions/GetTableOptionalParameters"
}
},
"required": [
"threadId",
"frameId",
"commandType"
]
},
"GetTableOptionalParameters": {
"type": "object",
"description": "Optional parameters for 'getTable' / 'getTableImage' operations.",
"properties": {
"start": {
"type": [
"integer",
@@ -90,13 +104,23 @@
"null"
],
"description": "Optional backend-specific format hint (e.g., 'json', 'csv', dtype/precision hints)."
},
"offset": {
"type": [
"integer",
"null"
],
"description": "Optional offset for image operations."
},
"imageId": {
"type": [
"string",
"null"
],
"description": "Optional image id for image operations."
}
},
"required": [
"threadId",
"frameId",
"commandType"
]
"additionalProperties": true
},
"GetTableResponse": {
"allOf": [
@@ -129,6 +153,63 @@
}
]
},
"GetTableImageRequest": {
"allOf": [
{
"$ref": "#/definitions/Request"
},
{
"type": "object",
"description": "Retrieve tabular data as an image",
"properties": {
"command": {
"type": "string",
"enum": [
"getTableImage"
]
},
"arguments": {
"$ref": "#/definitions/GetTableArguments"
}
},
"required": [
"command",
"arguments"
]
}
]
},
"GetTableImageResponse": {
"allOf": [
{
"$ref": "#/definitions/Response"
},
{
"type": "object",
"description": "Response to 'getTableImage' request.",
"properties": {
"command": {
"type": "string",
"enum": [
"getTableImage"
]
},
"body": {
"type": "object",
"properties": {
"result": {
"type": "string",
"description": "Opaque string payload with the result of the getTableImage operation."
}
}
}
},
"required": [
"body"
]
}
]
},
"GetArrayRequest": {
"allOf": [
{
@@ -17416,6 +17416,85 @@ class GetTableArguments(BaseSchema):
"INSPECTIONS"
]
},
"parametersJson": {
"type": "GetTableOptionalParameters"
}
}
__refs__ = set(['parametersJson'])
__slots__ = list(__props__.keys()) + ['kwargs']
def __init__(self, threadId, frameId, commandType, command=None, parametersJson=None, update_ids_from_dap=False, **kwargs): # noqa (update_ids_from_dap may be unused)
"""
:param ['string', 'integer'] threadId: Thread identifier where the frame/expression should be evaluated.
:param ['string', 'integer'] frameId: Frame identifier within the given thread.
:param string commandType:
:param string command: Python expression that evaluates to the table-like object (e.g., variable name or expression).
:param GetTableOptionalParameters parametersJson:
"""
self.threadId = threadId
self.frameId = frameId
self.commandType = commandType
self.command = command
if parametersJson is None:
self.parametersJson = GetTableOptionalParameters()
else:
self.parametersJson = GetTableOptionalParameters(update_ids_from_dap=update_ids_from_dap, **parametersJson) if parametersJson.__class__ != GetTableOptionalParameters else parametersJson
if update_ids_from_dap:
self.threadId = self._translate_id_from_dap(self.threadId)
self.frameId = self._translate_id_from_dap(self.frameId)
self.kwargs = kwargs
@classmethod
def update_dict_ids_from_dap(cls, dct):
if 'threadId' in dct:
dct['threadId'] = cls._translate_id_from_dap(dct['threadId'])
if 'frameId' in dct:
dct['frameId'] = cls._translate_id_from_dap(dct['frameId'])
return dct
def to_dict(self, update_ids_to_dap=False): # noqa (update_ids_to_dap may be unused)
threadId = self.threadId
frameId = self.frameId
commandType = self.commandType
command = self.command
parametersJson = self.parametersJson
if update_ids_to_dap:
if threadId is not None:
threadId = self._translate_id_to_dap(threadId)
if frameId is not None:
frameId = self._translate_id_to_dap(frameId)
dct = {
'threadId': threadId,
'frameId': frameId,
'commandType': commandType,
}
if command is not None:
dct['command'] = command
if parametersJson is not None:
dct['parametersJson'] = parametersJson.to_dict(update_ids_to_dap=update_ids_to_dap)
dct.update(self.kwargs)
return dct
@classmethod
def update_dict_ids_to_dap(cls, dct):
if 'threadId' in dct:
dct['threadId'] = cls._translate_id_to_dap(dct['threadId'])
if 'frameId' in dct:
dct['frameId'] = cls._translate_id_to_dap(dct['frameId'])
return dct
@register
class GetTableOptionalParameters(BaseSchema):
"""
Optional parameters for 'getTable' / 'getTableImage' operations.
Note: automatically generated code. Do not edit manually.
"""
__props__ = {
"start": {
"type": [
"integer",
@@ -17436,78 +17515,61 @@ class GetTableArguments(BaseSchema):
"null"
],
"description": "Optional backend-specific format hint (e.g., 'json', 'csv', dtype/precision hints)."
},
"offset": {
"type": [
"integer",
"null"
],
"description": "Optional offset for image operations."
},
"imageId": {
"type": [
"string",
"null"
],
"description": "Optional image id for image operations."
}
}
__refs__ = set()
__slots__ = list(__props__.keys()) + ['kwargs']
def __init__(self, threadId, frameId, commandType, command=None, start=None, end=None, format=None, update_ids_from_dap=False, **kwargs): # noqa (update_ids_from_dap may be unused)
def __init__(self, start=None, end=None, format=None, offset=None, imageId=None, update_ids_from_dap=False, **kwargs): # noqa (update_ids_from_dap may be unused)
"""
:param ['string', 'integer'] threadId: Thread identifier where the frame/expression should be evaluated.
:param ['string', 'integer'] frameId: Frame identifier within the given thread.
:param string commandType:
:param string command: Python expression that evaluates to the table-like object (e.g., variable name or expression).
:param ['integer', 'null'] start: Optional start row index (inclusive) for slice operations.
:param ['integer', 'null'] end: Optional end row index (exclusive) for slice operations.
:param ['string', 'null'] format: Optional backend-specific format hint (e.g., 'json', 'csv', dtype/precision hints).
:param ['integer', 'null'] offset: Optional offset for image operations.
:param ['string', 'null'] imageId: Optional image id for image operations.
"""
self.threadId = threadId
self.frameId = frameId
self.commandType = commandType
self.command = command
self.start = start
self.end = end
self.format = format
if update_ids_from_dap:
self.threadId = self._translate_id_from_dap(self.threadId)
self.frameId = self._translate_id_from_dap(self.frameId)
self.offset = offset
self.imageId = imageId
self.kwargs = kwargs
@classmethod
def update_dict_ids_from_dap(cls, dct):
if 'threadId' in dct:
dct['threadId'] = cls._translate_id_from_dap(dct['threadId'])
if 'frameId' in dct:
dct['frameId'] = cls._translate_id_from_dap(dct['frameId'])
return dct
def to_dict(self, update_ids_to_dap=False): # noqa (update_ids_to_dap may be unused)
threadId = self.threadId
frameId = self.frameId
commandType = self.commandType
command = self.command
start = self.start
end = self.end
format = self.format # noqa (assign to builtin)
if update_ids_to_dap:
if threadId is not None:
threadId = self._translate_id_to_dap(threadId)
if frameId is not None:
frameId = self._translate_id_to_dap(frameId)
offset = self.offset
imageId = self.imageId
dct = {
'threadId': threadId,
'frameId': frameId,
'commandType': commandType,
}
if command is not None:
dct['command'] = command
if start is not None:
dct['start'] = start
if end is not None:
dct['end'] = end
if format is not None:
dct['format'] = format
if offset is not None:
dct['offset'] = offset
if imageId is not None:
dct['imageId'] = imageId
dct.update(self.kwargs)
return dct
@classmethod
def update_dict_ids_to_dap(cls, dct):
if 'threadId' in dct:
dct['threadId'] = cls._translate_id_to_dap(dct['threadId'])
if 'frameId' in dct:
dct['frameId'] = cls._translate_id_to_dap(dct['frameId'])
return dct
@@ -17620,6 +17682,181 @@ class GetTableResponse(BaseSchema):
return dct
@register_request('getTableImage')
@register
class GetTableImageRequest(BaseSchema):
"""
Retrieve tabular data as an image
Note: automatically generated code. Do not edit manually.
"""
__props__ = {
"seq": {
"type": "integer",
"description": "Sequence number of the message (also known as message ID). The `seq` for the first message sent by a client or debug adapter is 1, and for each subsequent message is 1 greater than the previous message sent by that actor. `seq` can be used to order requests, responses, and events, and to associate requests with their corresponding responses. For protocol messages of type `request` the sequence number can be used to cancel the request."
},
"type": {
"type": "string",
"enum": [
"request"
]
},
"command": {
"type": "string",
"enum": [
"getTableImage"
]
},
"arguments": {
"type": "GetTableArguments"
}
}
__refs__ = set(['arguments'])
__slots__ = list(__props__.keys()) + ['kwargs']
def __init__(self, arguments, seq=-1, update_ids_from_dap=False, **kwargs): # noqa (update_ids_from_dap may be unused)
"""
:param string type:
:param string command:
:param GetTableArguments arguments:
:param integer seq: Sequence number of the message (also known as message ID). The `seq` for the first message sent by a client or debug adapter is 1, and for each subsequent message is 1 greater than the previous message sent by that actor. `seq` can be used to order requests, responses, and events, and to associate requests with their corresponding responses. For protocol messages of type `request` the sequence number can be used to cancel the request.
"""
self.type = 'request'
self.command = 'getTableImage'
if arguments is None:
self.arguments = GetTableArguments()
else:
self.arguments = GetTableArguments(update_ids_from_dap=update_ids_from_dap, **arguments) if arguments.__class__ != GetTableArguments else arguments
self.seq = seq
self.kwargs = kwargs
def to_dict(self, update_ids_to_dap=False): # noqa (update_ids_to_dap may be unused)
type = self.type # noqa (assign to builtin)
command = self.command
arguments = self.arguments
seq = self.seq
dct = {
'type': type,
'command': command,
'arguments': arguments.to_dict(update_ids_to_dap=update_ids_to_dap),
'seq': seq,
}
dct.update(self.kwargs)
return dct
@register_response('getTableImage')
@register
class GetTableImageResponse(BaseSchema):
"""
Response to 'getTableImage' request.
Note: automatically generated code. Do not edit manually.
"""
__props__ = {
"seq": {
"type": "integer",
"description": "Sequence number of the message (also known as message ID). The `seq` for the first message sent by a client or debug adapter is 1, and for each subsequent message is 1 greater than the previous message sent by that actor. `seq` can be used to order requests, responses, and events, and to associate requests with their corresponding responses. For protocol messages of type `request` the sequence number can be used to cancel the request."
},
"type": {
"type": "string",
"enum": [
"response"
]
},
"request_seq": {
"type": "integer",
"description": "Sequence number of the corresponding request."
},
"success": {
"type": "boolean",
"description": "Outcome of the request.\nIf True, the request was successful and the `body` attribute may contain the result of the request.\nIf the value is false, the attribute `message` contains the error in short form and the `body` may contain additional information (see `ErrorResponse.body.error`)."
},
"command": {
"type": "string",
"enum": [
"getTableImage"
]
},
"message": {
"type": "string",
"description": "Contains the raw error in short form if `success` is false.\nThis raw error might be interpreted by the client and is not shown in the UI.\nSome predefined values exist.",
"_enum": [
"cancelled",
"notStopped"
],
"enumDescriptions": [
"the request was cancelled.",
"the request may be retried once the adapter is in a 'stopped' state."
]
},
"body": {
"type": "object",
"properties": {
"result": {
"type": "string",
"description": "Opaque string payload with the result of the getTableImage operation."
}
}
}
}
__refs__ = set(['body'])
__slots__ = list(__props__.keys()) + ['kwargs']
def __init__(self, request_seq, success, body, seq=-1, message=None, update_ids_from_dap=False, **kwargs): # noqa (update_ids_from_dap may be unused)
"""
:param string type:
:param integer request_seq: Sequence number of the corresponding request.
:param boolean success: Outcome of the request.
If true, the request was successful and the `body` attribute may contain the result of the request.
If the value is false, the attribute `message` contains the error in short form and the `body` may contain additional information (see `ErrorResponse.body.error`).
:param string command:
:param GetTableImageResponseBody body:
:param integer seq: Sequence number of the message (also known as message ID). The `seq` for the first message sent by a client or debug adapter is 1, and for each subsequent message is 1 greater than the previous message sent by that actor. `seq` can be used to order requests, responses, and events, and to associate requests with their corresponding responses. For protocol messages of type `request` the sequence number can be used to cancel the request.
:param string message: Contains the raw error in short form if `success` is false.
This raw error might be interpreted by the client and is not shown in the UI.
Some predefined values exist.
"""
self.type = 'response'
self.request_seq = request_seq
self.success = success
self.command = 'getTableImage'
if body is None:
self.body = GetTableImageResponseBody()
else:
self.body = GetTableImageResponseBody(update_ids_from_dap=update_ids_from_dap, **body) if body.__class__ != GetTableImageResponseBody else body
self.seq = seq
self.message = message
self.kwargs = kwargs
def to_dict(self, update_ids_to_dap=False): # noqa (update_ids_to_dap may be unused)
type = self.type # noqa (assign to builtin)
request_seq = self.request_seq
success = self.success
command = self.command
body = self.body
seq = self.seq
message = self.message
dct = {
'type': type,
'request_seq': request_seq,
'success': success,
'command': command,
'body': body.to_dict(update_ids_to_dap=update_ids_to_dap),
'seq': seq,
}
if message is not None:
dct['message'] = message
dct.update(self.kwargs)
return dct
@register_request('getArray')
@register
class GetArrayRequest(BaseSchema):
@@ -20663,6 +20900,42 @@ class GetTableResponseBody(BaseSchema):
return dct
@register
class GetTableImageResponseBody(BaseSchema):
"""
"body" of GetTableImageResponse
Note: automatically generated code. Do not edit manually.
"""
__props__ = {
"result": {
"type": "string",
"description": "Opaque string payload with the result of the getTableImage operation."
}
}
__refs__ = set()
__slots__ = list(__props__.keys()) + ['kwargs']
def __init__(self, result=None, update_ids_from_dap=False, **kwargs): # noqa (update_ids_from_dap may be unused)
"""
:param string result: Opaque string payload with the result of the getTableImage operation.
"""
self.result = result
self.kwargs = kwargs
def to_dict(self, update_ids_to_dap=False): # noqa (update_ids_to_dap may be unused)
result = self.result
dct = {
}
if result is not None:
dct['result'] = result
dct.update(self.kwargs)
return dct
@register
class GetArrayResponseBody(BaseSchema):
"""
@@ -1,5 +1,5 @@
# Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
from _pydevd_bundle.tables.images.pydevd_image_loader import save_image_to_storage, DEFAULT_IMAGE_FORMAT
from _pydevd_bundle.custom.tables.images.pydevd_image_loader import save_image_to_storage, DEFAULT_IMAGE_FORMAT
def create_image(figure):
@@ -1,6 +1,6 @@
# Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
import numpy as np
from _pydevd_bundle.tables.images.pydevd_image_loader import (save_image_to_storage, GRAYSCALE_MODE, RGB_MODE, RGBA_MODE)
from _pydevd_bundle.custom.tables.images.pydevd_image_loader import (save_image_to_storage, GRAYSCALE_MODE, RGB_MODE, RGBA_MODE)
try:
import tensorflow as tf
@@ -1,6 +1,6 @@
# Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
import numpy as np
from _pydevd_bundle.tables.images.pydevd_image_loader import (save_image_to_storage, GRAYSCALE_MODE, RGB_MODE, RGBA_MODE)
from _pydevd_bundle.custom.tables.images.pydevd_image_loader import (save_image_to_storage, GRAYSCALE_MODE, RGB_MODE, RGBA_MODE)
MAX_PIXELS = 144_000_000
MAX_DIMENSION = 15_000
@@ -1,6 +1,6 @@
# Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
import PIL
from _pydevd_bundle.tables.images.pydevd_image_loader import save_image_to_storage, DEFAULT_IMAGE_FORMAT
from _pydevd_bundle.custom.tables.images.pydevd_image_loader import save_image_to_storage, DEFAULT_IMAGE_FORMAT
def create_image(pillow_image):
# type: (PIL.Image.Image) -> str
@@ -25,7 +25,7 @@ from _pydevd_bundle.pydevd_comm import (
internal_get_exception_details_json,
internal_step_in_thread,
internal_smart_step_into,
InternalTableCommand
InternalTableCommand, InternalTableImageStartCommand, InternalTableImageChunkCommand
)
from _pydevd_bundle.pydevd_comm_constants import (
CMD_THREAD_SUSPEND,
@@ -346,6 +346,14 @@ class PyDevdAPI(object):
int_cmd = InternalTableCommand(seq, thread_id, frame_id, init_command, command_type, start_index, end_index, format)
py_db.post_internal_command(int_cmd, thread_id)
def request_get_image_start(self, py_db, seq, thread_id, frame_id, init_command, command_type):
int_cmd = InternalTableImageStartCommand(seq, thread_id, frame_id, init_command, command_type)
py_db.post_internal_command(int_cmd, thread_id)
def request_get_image_load_chunk(self, py_db, seq, thread_id, frame_id, init_command, command_type, offset, image_id):
int_cmd = InternalTableImageChunkCommand(seq, thread_id, frame_id, init_command, command_type, offset, image_id)
py_db.post_internal_command(int_cmd, thread_id)
def request_load_full_value(self, py_db, seq, thread_id, frame_id, vars):
int_cmd = InternalLoadFullValue(seq, thread_id, frame_id, vars)
py_db.post_internal_command(int_cmd, thread_id)
@@ -95,7 +95,8 @@ from _pydevd_bundle._debug_adapter.pydevd_schema import (
)
from _pydevd_bundle._debug_adapter import pydevd_base_schema, pydevd_schema
from _pydevd_bundle.pydevd_net_command import NetCommand
from _pydevd_bundle.custom.pydevd_tables import exec_table_command
from _pydevd_bundle.custom.pydevd_tables import exec_table_command, \
exec_image_table_command
from _pydevd_bundle.pydevd_xml import ExceptionOnEvaluate
from _pydevd_bundle.pydevd_constants import ForkSafeLock, NULL
from _pydevd_bundle.pydevd_daemon_thread import PyDBDaemonThread
@@ -1948,18 +1949,13 @@ class InternalTableCommand(InternalThreadCommand):
def do_it(self, dbg):
try:
pydev_log.info(f"WE ARE IN INTERNAL TABLE COMMAND, thread_id: {self.thread_id}, frame_id: {self.frame_id}" )
frame = dbg.find_frame(self.thread_id, self.frame_id)
pydev_log.info("frame = dbg.find_frame(self.thread_id, self.frame_id)")
pydev_log.info(f"frame {frame}")
success, res = self.exec_command(frame)
if success:
pydev_log.info("success")
cmd = dbg.cmd_factory.make_get_table_message(self.sequence, res)
dbg.writer.add_command(cmd)
else:
pydev_log.info(f"error, no success, res: {res}")
cmd = dbg.cmd_factory.make_error_message(self.sequence, str(res))
dbg.writer.add_command(cmd)
except Exception as e:
@@ -1970,3 +1966,65 @@ class InternalTableCommand(InternalThreadCommand):
return exec_table_command(self.init_command, self.command_type,
self.start_index, self.end_index, self.format,
frame.f_globals, frame.f_locals)
#=======================================================================================================================
# DebugImageViewerAction
#=======================================================================================================================
class InternalTableImageCommandBase(InternalThreadCommand):
def __init__(self, sequence, thread_id, frame_id, init_command, command_type):
InternalThreadCommand.__init__(self, thread_id)
self.sequence = sequence
self.frame_id = frame_id
self.init_command = init_command
self.command_type = command_type
def do_it(self, dbg):
try:
frame = dbg.find_frame(self.thread_id, self.frame_id)
success, res = self.exec_command(frame)
if success:
cmd = dbg.cmd_factory.make_get_table_image_message(self.sequence, res, self.get_command_id())
dbg.writer.add_command(cmd)
else:
cmd = dbg.cmd_factory.make_error_message(self.sequence, str(res))
dbg.writer.add_command(cmd)
except Exception as e:
cmd = dbg.cmd_factory.make_error_message(self.sequence, get_exception_traceback_str())
dbg.writer.add_command(cmd)
def get_command_id(self):
raise NotImplementedError()
def exec_command(self, frame):
return exec_image_table_command(self.init_command, self.command_type,
self.get_offset(), self.get_image_id(),
frame.f_globals, frame.f_locals)
def get_offset(self):
return None
def get_image_id(self):
return None
class InternalTableImageStartCommand(InternalTableImageCommandBase):
def get_command_id(self):
return CMD_IMAGE_COMMAND_START_LOAD
class InternalTableImageChunkCommand(InternalTableImageCommandBase):
def __init__(self, sequence, thread_id, frame_id, init_command, command_type, offset, image_id):
InternalTableImageCommandBase.__init__(self, sequence, thread_id, frame_id, init_command, command_type)
self._offset = offset
self._image_id = image_id
def get_command_id(self):
return CMD_IMAGE_COMMAND_CHUNK_LOAD
def get_offset(self):
return self._offset
def get_image_id(self):
return self._image_id
@@ -107,6 +107,9 @@ CMD_RETURN = 502
CMD_SET_PROTOCOL = 503
CMD_ERROR = 901
CMD_IMAGE_COMMAND_START_LOAD = 213
CMD_IMAGE_COMMAND_CHUNK_LOAD = 214
# this number can be changed if there's need to do so
# if the io is too big, we'll not send all (could make the debugger too non-responsive)
MAX_IO_MSG_SIZE = 10000
@@ -191,10 +194,13 @@ ID_TO_MEANING = {
"206": "CMD_STEP_INTO_COROUTINE",
"207": "CMD_LOAD_SOURCE_FROM_FRAME_ID",
"211": "CMD_TABLE_EXEC",
"213": "CMD_IMAGE_COMMAND_START_LOAD",
"214": "CMD_IMAGE_COMMAND_CHUNK_LOAD",
"501": "CMD_VERSION",
"502": "CMD_RETURN",
"503": "CMD_SET_PROTOCOL",
"901": "CMD_ERROR",
}
@@ -16,7 +16,7 @@ from _pydevd_bundle._debug_adapter.pydevd_schema import (
OutputEvent,
ContinuedEventBody,
ExitedEventBody,
ExitedEvent,
ExitedEvent, GetTableImageResponseBody,
)
from _pydevd_bundle.pydevd_comm_constants import (
CMD_THREAD_CREATE,
@@ -596,15 +596,21 @@ This may mean a number of things:
def make_get_table_message(self, seq, res):
try:
body = GetTableResponseBody(result=res)
pydev_log.info(f"RESPONSE BODY: {body}")
response = pydevd_schema.GetTableResponse(request_seq=seq, success=True, body=body)
pydev_log.info(f"RESPONSE: {response}")
return NetCommand(CMD_RETURN, 0, response, is_json=True)
except Exception as e:
pydev_log.exception(f"Error while building getTable response: {e}")
err_response = pydevd_schema.GetTableResponse(request_seq=seq, success=False, body={})
return NetCommand(CMD_RETURN, 0, err_response, is_json=True)
def make_get_table_image_message(self, seq, res, int_cmd):
try:
body = GetTableImageResponseBody(result=res)
response = pydevd_schema.GetTableImageResponse(request_seq=seq, success=True, body=body)
return NetCommand(int_cmd, 0, response, is_json=True)
except Exception as e:
err_response = pydevd_schema.GetTableResponse(request_seq=seq, success=False, body={})
return NetCommand(int_cmd, 0, err_response, is_json=True)
@overrides(NetCommandFactory.make_get_array_message)
def make_get_array_message(self, seq, res):
try:
@@ -612,6 +618,5 @@ This may mean a number of things:
response = pydevd_schema.GetArrayResponse(request_seq=seq, success=True, body=body)
return NetCommand(CMD_RETURN, 0, response, is_json=True)
except Exception as e:
pydev_log.exception(f"Error while building getTable response: {e}")
err_response = pydevd_schema.GetArrayResponse(request_seq=seq, success=False, body={})
return NetCommand(CMD_RETURN, 0, err_response, is_json=True)
@@ -35,6 +35,7 @@ from _pydevd_bundle._debug_adapter.pydevd_schema import (
BreakpointEventBody,
InitializedEvent,
)
from _pydevd_bundle.custom.pydevd_tables import TableCommandType
from _pydevd_bundle.pydevd_api import PyDevdAPI
from _pydevd_bundle.pydevd_breakpoints import get_exception_class, FunctionBreakpoint
from _pydevd_bundle.pydevd_comm_constants import (
@@ -1359,10 +1360,11 @@ class PyDevJsonCommandProcessor(object):
frame_id = args.frameId
init_command = args.command
command_type = args.commandType
start_index = args.start
end_index = args.end
df_format = args.format
start_index = args.parametersJson.start
end_index = args.parametersJson.end
df_format = args.parametersJson.format
error_msg = self.api.request_get_table(py_db, request.seq, thread_id, frame_id, init_command, command_type, start_index, end_index, df_format)
if error_msg:
response = pydevd_base_schema.build_response(
request,
@@ -1372,7 +1374,6 @@ class PyDevJsonCommandProcessor(object):
"message": error_msg,
},
)
pydev_log.error("ERR WHILE EXECUTING GETTABLE" + error_msg)
return NetCommand(CMD_RETURN, 0, response, is_json=True)
return None
@@ -1396,6 +1397,32 @@ class PyDevJsonCommandProcessor(object):
"message": error_msg,
},
)
pydev_log.error("ERR WHILE EXECUTING GETTABLE" + error_msg)
return NetCommand(CMD_RETURN, 0, response, is_json=True)
return None
def on_gettableimage_request(self, py_db, request):
args = request.arguments
thread_id = args.threadId
frame_id = args.frameId
init_command = args.command
command_type = args.commandType
offset = args.parametersJson.offset
image_id = args.parametersJson.imageId
if command_type == TableCommandType.IMAGE_CHUNK_LOAD:
error_msg = self.api.request_get_image_load_chunk(py_db, request.seq, thread_id, frame_id, init_command, command_type, offset, image_id)
elif command_type == TableCommandType.IMAGE_START_CHUNK_LOAD:
error_msg = self.api.request_get_image_start(py_db, request.seq, thread_id, frame_id, init_command, command_type)
else:
error_msg = f"Unknown command type: {command_type}"
if error_msg:
response = pydevd_base_schema.build_response(
request,
kwargs={
"body": {},
"success": False,
"message": error_msg,
},
)
return NetCommand(CMD_RETURN, 0, response, is_json=True)
return None
@@ -74,6 +74,10 @@ class _AbstractVariable(object):
elif has_attribute_safe(self.value, '__len__') and not is_string(self.value):
shape = str(len(self.value))
attributes.append(f"shape: {shape}")
if has_attribute_safe(self.value, "dtype"):
dtype = str(self.value.dtype)
attributes.append(f"dtype: {dtype}")
except:
pass
@@ -34,7 +34,7 @@ import java.util.regex.Pattern;
import static com.jetbrains.python.debugger.PyDebugValueGroupsKt.*;
public class PyDebugValue extends XNamedValue {
public class PyDebugValue extends XNamedValue implements PyXDebugValue {
protected static final Logger LOG = Logger.getInstance(PyDebugValue.class);
private static final String ARRAY = "Array";
private static final String DATA_FRAME = "DataFrame";
@@ -107,12 +107,13 @@ public class PyDebugValue extends XNamedValue {
@Nullable String typeQualifier,
final @Nullable String value,
@Nullable String shape,
@Nullable String arrayElementType,
boolean isReturnedVal,
boolean isIPythonHidden,
boolean errorOnEval,
@Nullable String typeRendererId,
final @NotNull PyFrameAccessor frameAccessor) {
this(name, type, typeQualifier, value, EVALUATOR_POSTFIXES.containsKey(type), shape, isReturnedVal, isIPythonHidden, errorOnEval, typeRendererId, null,
this(name, type, typeQualifier, value, EVALUATOR_POSTFIXES.containsKey(type), shape, arrayElementType, isReturnedVal, isIPythonHidden, errorOnEval, typeRendererId, null,
frameAccessor);
}
@@ -218,6 +219,7 @@ public class PyDebugValue extends XNamedValue {
myTempName = tempName;
}
@Override
public @Nullable String getType() {
return myType;
}
@@ -551,10 +553,14 @@ public class PyDebugValue extends XNamedValue {
node.setFullValueEvaluator(new PyNumericContainerValueEvaluator(linkText, myFrameAccessor, treeName));
}
private static void addViewAsImageLink(XValueNodeEx valueNode) {
protected static void addViewAsImageLink(XValueNodeEx valueNode) {
PyDebugValue debugValue = (PyDebugValue)valueNode.getXValue();
if (!checkAndShowViewAsImageOnScreen(debugValue))
return;
addViewAsImageHyperlink(valueNode);
}
protected static void addViewAsImageHyperlink(XValueNodeEx valueNode) {
String viewAsImageText = PydevBundle.message("pydev.view.as.image");
valueNode.addAdditionalHyperlink(new XDebuggerTreeNodeHyperlink(viewAsImageText) {
@Override
@@ -577,7 +583,7 @@ public class PyDebugValue extends XNamedValue {
});
}
private static boolean checkAndShowViewAsImageOnScreen(PyDebugValue debugValue) {
protected static boolean checkAndShowViewAsImageOnScreen(PyDebugValue debugValue) {
return Registry.is("actions.show.as.image.visibility", false)
&& !PluginManagerCore.isDisabled(PluginManagerCore.ULTIMATE_PLUGIN_ID)
&& checkAndEnableViewAsImageVisibility(debugValue);
@@ -697,10 +703,12 @@ public class PyDebugValue extends XNamedValue {
}
}
@Override
public @NotNull PyFrameAccessor getFrameAccessor() {
return myFrameAccessor;
}
@Override
public void setFrameAccessor(@NotNull PyFrameAccessor frameAccessor) {
myFrameAccessor = frameAccessor;
}
@@ -0,0 +1,8 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.debugger;
interface PyXDebugValue {
fun getName(): String
val type: String?
var frameAccessor: PyFrameAccessor
}
@@ -39,7 +39,7 @@ internal class PyDebuggerEvaluator(private val myProject: Project, private val m
// todo: think on getting results from EXEC
val value = myDebugProcess.evaluate(expression, !isExpression, doTrunc)
if (value.isErrorOnEval) {
callback.errorOccurred("{" + value.getType() + "}" + value.value) //NON-NLS
callback.errorOccurred("{" + value.type + "}" + value.value) //NON-NLS
}
else {
callback.evaluated(value)