[debugger] PY-84040 add custom variable rendering and variable view features to python dap, like it looks in default pydevd

maximpopov/PY-84040-debugpy-Implement-custom-variable-renderers-(like-it-looks-in-PyCharm)

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

GitOrigin-RevId: 42bdef24e1935592a14610e6a9dd80f70143e5dc
This commit is contained in:
Maxim Popov
2025-10-27 16:08:27 +00:00
committed by intellij-monorepo-bot
parent 140fdd8d75
commit 5e2fb82872
8 changed files with 589 additions and 52 deletions
@@ -1,3 +1,5 @@
import sys
try:
xrange = xrange
except:
@@ -6,3 +8,105 @@ except:
NUMPY_NUMERIC_TYPES = "biufc"
NUMPY_FLOATING_POINT_TYPES = "fc"
IS_PYCHARM = True
#=======================================================================================================================
# Python 3?
#=======================================================================================================================
IS_PY3K = False
IS_PY34_OR_GREATER = False
IS_PY36_OR_GREATER = False
IS_PY37_OR_GREATER = False
IS_PY36_OR_LESSER = False
IS_PY38_OR_GREATER = False
IS_PY38 = False
IS_PY39 = False
IS_PY39_OR_GREATER = False
IS_PY310 = False
IS_PY310_OR_GREATER = False
IS_PY311 = False
IS_PY311_OR_GREATER = False
IS_PY312_OR_GREATER = False
IS_PY312_OR_LESSER = False
IS_PY313 = False
IS_PY313_OR_GREATER = False
IS_PY313_OR_LESSER = False
IS_PY314 = False
IS_PY2 = True
IS_PY27 = False
IS_PY24 = False
try:
if sys.version_info[0] >= 3:
IS_PY3K = True
IS_PY2 = False
IS_PY34_OR_GREATER = sys.version_info >= (3, 4)
IS_PY36_OR_GREATER = sys.version_info >= (3, 6)
IS_PY37_OR_GREATER = sys.version_info >= (3, 7)
IS_PY36_OR_LESSER = sys.version_info[:2] <= (3, 6)
IS_PY38 = sys.version_info[0] == 3 and sys.version_info[1] == 8
IS_PY38_OR_GREATER = sys.version_info >= (3, 8)
IS_PY39 = sys.version_info[0] == 3 and sys.version_info[1] == 9
IS_PY39_OR_GREATER = sys.version_info >= (3, 9)
IS_PY310 = sys.version_info[0] == 3 and sys.version_info[1] == 10
IS_PY310_OR_GREATER = sys.version_info >= (3, 10)
IS_PY311 = sys.version_info[0] == 3 and sys.version_info[1] == 11
IS_PY311_OR_GREATER = sys.version_info >= (3, 11)
IS_PY312_OR_GREATER = sys.version_info >= (3, 12)
IS_PY312_OR_LESSER = sys.version_info[:2] <= (3, 12)
IS_PY313 = sys.version_info[0] == 3 and sys.version_info[1] == 13
IS_PY313_OR_GREATER = sys.version_info >= (3, 13)
IS_PY313_OR_LESSER = sys.version_info[:2] <= (3, 13)
IS_PY314 = sys.version_info[0] == 3 and sys.version_info[1] == 14
elif sys.version_info[0] == 2 and sys.version_info[1] == 7:
IS_PY27 = True
elif sys.version_info[0] == 2 and sys.version_info[1] == 4:
IS_PY24 = True
except AttributeError:
pass # Not all versions have sys.version_info
if IS_PY3K:
def dict_keys(d):
return list(d.keys())
def dict_values(d):
return list(d.values())
dict_iter_values = dict.values
def dict_iter_items(d):
return d.items()
def dict_items(d):
return list(d.items())
else:
def dict_keys(d):
return d.keys()
try:
dict_iter_values = dict.itervalues
except:
try:
dict_iter_values = dict.values # Older versions don't have the itervalues
except:
def dict_iter_values(d):
return d.values()
try:
dict_values = dict.values
except:
def dict_values(d):
return d.values()
def dict_iter_items(d):
try:
return d.iteritems()
except:
return d.items()
def dict_items(d):
return d.items()
@@ -0,0 +1,283 @@
# Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
from array import array
from collections import deque
from _pydev_bundle import pydev_log
from _pydevd_bundle.custom.pydevd_constants import IS_PY3K
from _pydevd_bundle.custom.pydevd_utils import take_first_n_coll_elements
# Maximum final result string length
MAX_REPR_LENGTH = 1000
# Maximum number of value elements
MAX_REPR_ITEM_SIZE = 256
DEFAULT_FORMAT = '%s'
def _get_ndarray_variable_repr(num_array, max_items=MAX_REPR_ITEM_SIZE):
# ndarray.__str__() is already optimised and works fast enough
if num_array.ndim == 0:
return str(num_array).replace('\n', ',').strip()
else:
return str(num_array[:max_items]).replace('\n', ',').strip()
def _get_series_variable_repr(series, max_items=MAX_REPR_ITEM_SIZE):
res = []
total_length = 0
series = series.iloc[:max_items]
for item in series.items():
# item: (index, value)
item_repr = str(item)
res.append(item_repr)
total_length += len(item_repr)
if total_length > MAX_REPR_LENGTH:
break
return ' '.join(res)
def _get_df_variable_repr(data_frame):
# Avoid using df.iteritems() or df.values[i], because it works very slow for
# large data frames. df.__str__() is already optimised and works fast enough.
data_preview = []
column_row = 0
shape_rows, shape_cols = data_frame.shape
if shape_cols > 1000 or shape_rows > 10000:
head_number = 1
else:
head_number = 3
rows = str(data_frame.head(head_number)).split('\n')
for (i, r) in enumerate(rows):
if i != column_row:
data_preview.append("[%s]" % r)
if r == '':
column_row = i + 1
# The string provided is used for column name completion
# by JupyterVarsFrameExecutor.parseFrameVars
return '%s %s' % (list(data_frame.columns), ' '.join(data_preview))
def _trim_string_repr_if_needed(value, do_trim=True, max_length=MAX_REPR_LENGTH):
if len(value) > max_length and do_trim:
value = value[:max_length]
value += '...'
return value
def _get_external_collection_repr(collection, raise_exception=False):
typename = type(collection).__name__
typename_with_package = type(collection)
# pandas var
try:
if typename == "Series" or typename == "GeoSeries":
return _get_series_variable_repr(collection)
if typename == "DataFrame" or typename == "GeoDataFrame":
return _get_df_variable_repr(collection)
except Exception as e:
pydev_log.warn("Failed to format pandas variable: " + str(e))
if raise_exception:
raise e
# ndarray and other numpy types
try:
if typename == 'ndarray' or "numpy." in str(typename_with_package):
return _get_ndarray_variable_repr(collection)
except Exception as e:
pydev_log.warn("Failed to format numpy ndarray: " + str(e))
if raise_exception:
raise e
return None
pydevd_repr_function_python2 = None
if IS_PY3K:
from reprlib import Repr
from itertools import islice
def _possibly_sorted(x):
# Since not all sequences of items can be sorted and comparison
# functions may raise arbitrary exceptions, return an unsorted
# sequence in that case.
try:
return sorted(x)
except Exception:
return list(x)
class PydevdRepr(Repr):
def __init__(self, do_trim):
super(PydevdRepr, self).__init__()
self.fillvalue = '...'
self.maxdict = MAX_REPR_ITEM_SIZE
self.maxlist = MAX_REPR_ITEM_SIZE
self.maxtuple = MAX_REPR_ITEM_SIZE
self.maxset = MAX_REPR_ITEM_SIZE
self.maxfrozenset = MAX_REPR_ITEM_SIZE
self.maxdeque = MAX_REPR_ITEM_SIZE
self.maxarray = MAX_REPR_ITEM_SIZE
self.maxlong = MAX_REPR_ITEM_SIZE
self.maxstring = MAX_REPR_ITEM_SIZE
self.maxother = MAX_REPR_ITEM_SIZE
self.do_trim = do_trim
def _repr_iterable(self, x, level, left, right, maxiter, trail=''):
n = len(x)
if level <= 0 and n:
s = self.fillvalue
else:
newlevel = level - 1
repr1 = self.repr1
pieces = []
curr_length = 0
max_elements = maxiter if self.do_trim else n
for elem in islice(x, max_elements):
elem_repr = repr1(elem, newlevel)
curr_length += len(elem_repr)
pieces.append(elem_repr)
if curr_length >= MAX_REPR_LENGTH and self.do_trim:
break
if (n > maxiter or curr_length >= MAX_REPR_LENGTH) and self.do_trim:
pieces.append(self.fillvalue)
s = ', '.join(pieces)
if n == 1 and trail:
right = trail + right
return '%s%s%s' % (left, s, right)
def repr_str(self, x, level):
if level == self.maxlevel:
if self.do_trim:
return x[:self.maxstring]
else:
return x
else:
if self.do_trim:
return super().repr_str(x, level)
else:
return "'{x}'".format(x=x)
def repr_dict(self, x, level):
n = len(x)
if n == 0: return '{}'
if level <= 0: return '{...}'
newlevel = level - 1
repr1 = self.repr1
pieces = []
curr_length = 0
max_elements = self.maxdict if self.do_trim else n
for key in islice(_possibly_sorted(x), max_elements):
keyrepr = repr1(key, newlevel)
valrepr = repr1(x[key], newlevel)
elem_repr = '%s: %s' % (keyrepr, valrepr)
pieces.append(elem_repr)
curr_length += len(elem_repr)
if curr_length >= MAX_REPR_LENGTH and self.do_trim:
break
if (n > self.maxdict or curr_length >= MAX_REPR_LENGTH) and self.do_trim:
pieces.append(self.fillvalue)
s = ', '.join(pieces)
return '{%s}' % (s,)
def repr_instance(self, x, level):
# pandas series, ds | ndarray
result = _get_external_collection_repr(x)
if result is not None:
return result
# if `__repr__` is overridden, then use `reprlib`
if x.__class__.__repr__ != object.__repr__:
if self.do_trim:
return super().repr_instance(x, level)
return repr(x)
# if `__str__` is overridden, then return str(x)
if x.__class__.__str__ != object.__str__:
return str(x)
if self.do_trim:
return super().repr_instance(x, level)
return '%s' % x
else:
def pydevd_repr_function(value, do_trim=True):
# pandas series, ds | ndarray
result = _get_external_collection_repr(value, True)
if result is not None:
return result
limited_size_collection_classes = [
list, tuple, set, frozenset, dict, array, deque, str,
]
if IS_PY3K:
limited_size_collection_classes.append(bytes)
else:
limited_size_collection_classes.append(unicode)
if hasattr(value, '__class__'):
if value.__class__ in limited_size_collection_classes:
if len(value) > MAX_REPR_ITEM_SIZE and do_trim:
return ('%s' % take_first_n_coll_elements(value, MAX_REPR_ITEM_SIZE)).rstrip(')]}') + '...'
return None
# if `__repr__` is overridden, then return repr(value)
if hasattr(value.__class__, "__repr__"):
if do_trim:
return repr(value)[:MAX_REPR_LENGTH]
else:
return repr(value)
# else
if do_trim:
return str(value)[:MAX_REPR_LENGTH]
else:
return str(value)
pydevd_repr_function_python2 = pydevd_repr_function
def get_value_repr(value, do_trim=True, format=DEFAULT_FORMAT):
"""
Returns string representation of any value
:param value: target value
:param bool do_trim: is truncated representation
:param str format: formatting string (format % value)
:return: string representation of target value
:rtype: str
"""
value_representation = None
try:
try:
if format != DEFAULT_FORMAT:
value_representation = format % value
else:
if IS_PY3K:
pydevd_repr_fun = PydevdRepr(do_trim).repr
value_representation = pydevd_repr_fun(value)
else:
value_representation = pydevd_repr_function_python2(value, do_trim)
except Exception as e:
pydev_log.warn("Failed to get repr for a value: " + str(e))
if value_representation is None:
value_representation = format % value
if do_trim:
return _trim_string_repr_if_needed(value_representation, do_trim)
else:
return value_representation
except:
try:
return _trim_string_repr_if_needed(repr(value), do_trim)
except:
return 'Unable to get repr for %s' % value.__class__
@@ -1,3 +1,14 @@
from array import array
from collections import deque
from _pydevd_bundle.custom.pydevd_asyncio_provider import \
get_eval_async_expression_in_context
from _pydevd_bundle.custom.pydevd_constants import dict_iter_items
try:
from collections import OrderedDict
except:
OrderedDict = dict
class VariableWithOffset(object):
def __init__(self, data, offset):
self.data, self.offset = data, offset
@@ -9,3 +20,28 @@ def eval_expression(expression, globals, locals):
return eval_func(expression, globals, locals, False)
return eval(expression, globals, locals)
def get_var_and_offset(var):
if isinstance(var, VariableWithOffset):
return var.data, var.offset
return var, 0
def take_first_n_coll_elements(coll, n):
if coll.__class__ in (list, tuple, array, str):
return coll[:n]
elif coll.__class__ in (set, frozenset, deque):
buf = []
for i, x in enumerate(coll):
if i >= n:
break
buf.append(x)
return type(coll)(buf)
elif coll.__class__ in (dict, OrderedDict):
ret = type(coll)()
for i, (k, v) in enumerate(dict_iter_items(coll)):
if i >= n:
break
ret[k] = v
return ret
else:
raise TypeError("Unsupported collection type: '%s'" % str(coll.__class__))
@@ -810,7 +810,7 @@ def get_var_scope(attr_name, attr_value, evaluate_name, handle_return_values):
elif attr_name == GENERATED_LEN_ATTR_NAME:
return ""
if attr_name.startswith("__") and attr_name.endswith("__"):
if (attr_name.startswith("__") and attr_name.endswith("__")) or inspect.ismodule(attr_value):
return DAPGrouper.SCOPE_SPECIAL_VARS
if attr_name.startswith("_") or attr_name.endswith("__"):
@@ -818,10 +818,10 @@ def get_var_scope(attr_name, attr_value, evaluate_name, handle_return_values):
try:
if inspect.isroutine(attr_value) or isinstance(attr_value, MethodWrapperType):
return DAPGrouper.SCOPE_FUNCTION_VARS
return DAPGrouper.SCOPE_SPECIAL_VARS # note: we changed the scope here to correspond the scopes of pycharm's pydevd
elif inspect.isclass(attr_value):
return DAPGrouper.SCOPE_CLASS_VARS
return DAPGrouper.SCOPE_SPECIAL_VARS # note: we changed the scope here to correspond the scopes of pycharm's pydevd
except:
# It's possible that isinstance throws an exception when dealing with user-code.
if DebugInfoHolder.DEBUG_TRACE_LEVEL > 0:
@@ -330,10 +330,10 @@ class DAPGrouper(object):
the xml protocol the type is just added to each variable and the UI can group/hide it as needed.
"""
SCOPE_SPECIAL_VARS = "special variables"
SCOPE_PROTECTED_VARS = "protected variables"
SCOPE_FUNCTION_VARS = "function variables"
SCOPE_CLASS_VARS = "class variables"
SCOPE_SPECIAL_VARS = "Special Variables"
SCOPE_PROTECTED_VARS = "Protected Attributes"
SCOPE_FUNCTION_VARS = "Function Variables"
SCOPE_CLASS_VARS = "Class Variables"
SCOPES_SORTED = [
SCOPE_SPECIAL_VARS,
@@ -1,10 +1,23 @@
from _pydevd_bundle.pydevd_extension_api import TypeResolveProvider
from _pydevd_bundle.custom.pydevd_utils import get_var_and_offset
from _pydevd_bundle.pydevd_extension_api import TypeResolveProvider, \
StrPresentationProvider
from _pydevd_bundle.pydevd_resolver import defaultResolver
from .pydevd_helpers import find_mod_attr
from _pydevd_bundle import pydevd_constants
from _pydevd_bundle.custom.pydevd_repr_utils import get_value_repr
import inspect
try:
from collections import OrderedDict
except:
OrderedDict = dict
TOO_LARGE_MSG = "Maximum number of items (%s) reached. To show more items customize the value of the PYDEVD_CONTAINER_NUMPY_MAX_ITEMS environment variable."
TOO_LARGE_ATTR = "Unable to handle:"
IS_PYCHARM = True
MAX_ITEMS_TO_HANDLE = 300 if not IS_PYCHARM else 100
DEFAULT_PRECISION = 5
class NdArrayItemsContainer(object):
@@ -17,74 +30,135 @@ class NDArrayTypeResolveProvider(object):
"""
def can_provide(self, type_object, type_name):
nd_array = find_mod_attr("numpy", "ndarray")
return nd_array is not None and issubclass(type_object, nd_array)
nd_array = find_mod_attr('numpy', 'ndarray')
return nd_array is not None and inspect.isclass(type_object) and issubclass(type_object, nd_array)
'''
This resolves a numpy ndarray returning some metadata about the NDArray
'''
def is_numeric(self, obj):
if not hasattr(obj, "dtype"):
if not hasattr(obj, 'dtype'):
return False
return obj.dtype.kind in "biufc"
return obj.dtype.kind in 'biufc'
def round_if_possible(self, obj):
try:
return obj.round(DEFAULT_PRECISION)
except TypeError:
return obj
def resolve(self, obj, attribute):
if attribute == "__internals__":
return defaultResolver.get_dictionary(obj)
if attribute == "min":
if self.is_numeric(obj) and obj.size > 0:
if attribute == '__internals__':
if not IS_PYCHARM:
return defaultResolver.get_dictionary(obj)
if attribute == 'min':
if self.is_numeric(obj):
return obj.min()
else:
return None
if attribute == "max":
if self.is_numeric(obj) and obj.size > 0:
if attribute == 'max':
if self.is_numeric(obj):
return obj.max()
else:
return None
if attribute == "shape":
if attribute == 'shape':
return obj.shape
if attribute == "dtype":
if attribute == 'dtype':
return obj.dtype
if attribute == "size":
if attribute == 'size':
return obj.size
if attribute.startswith("["):
if attribute.startswith('['):
container = NdArrayItemsContainer()
i = 0
format_str = "%0" + str(int(len(str(len(obj))))) + "d"
format_str = '%0' + str(int(len(str(len(obj))))) + 'd'
for item in obj:
setattr(container, format_str % i, item)
i += 1
if i >= pydevd_constants.PYDEVD_CONTAINER_NUMPY_MAX_ITEMS:
setattr(container, TOO_LARGE_ATTR, TOO_LARGE_MSG % (pydevd_constants.PYDEVD_CONTAINER_NUMPY_MAX_ITEMS,))
if i > MAX_ITEMS_TO_HANDLE:
setattr(container, TOO_LARGE_ATTR, TOO_LARGE_MSG)
break
return container
if IS_PYCHARM and attribute == 'array':
container = NdArrayItemsContainer()
container.items = obj
return container
return None
def get_dictionary(self, obj):
ret = dict()
ret["__internals__"] = defaultResolver.get_dictionary(obj)
if not IS_PYCHARM:
ret['__internals__'] = defaultResolver.get_dictionary(obj)
if obj.size > 1024 * 1024:
ret["min"] = "ndarray too big, calculating min would slow down debugging"
ret["max"] = "ndarray too big, calculating max would slow down debugging"
elif obj.size == 0:
ret["min"] = "array is empty"
ret["max"] = "array is empty"
ret['min'] = 'ndarray too big, calculating min would slow down debugging'
ret['max'] = 'ndarray too big, calculating max would slow down debugging'
else:
if self.is_numeric(obj):
ret["min"] = obj.min()
ret["max"] = obj.max()
ret['min'] = obj.min()
ret['max'] = obj.max()
else:
ret["min"] = "not a numeric object"
ret["max"] = "not a numeric object"
ret["shape"] = obj.shape
ret["dtype"] = obj.dtype
ret["size"] = obj.size
try:
ret["[0:%s] " % (len(obj))] = list(obj[0 : pydevd_constants.PYDEVD_CONTAINER_NUMPY_MAX_ITEMS])
except:
# This may not work depending on the array shape.
pass
ret['min'] = 'not a numeric object'
ret['max'] = 'not a numeric object'
ret['shape'] = obj.shape
ret['dtype'] = obj.dtype
ret['size'] = obj.size
if IS_PYCHARM:
container = NdArrayItemsContainer()
container.items = obj
ret['array'] = container
else:
ret['[0:%s] ' % (len(obj))] = list(obj[0:MAX_ITEMS_TO_HANDLE])
return ret
class NDArrayStrProvider(StrPresentationProvider):
def can_provide(self, type_object, type_name):
nd_array = find_mod_attr('numpy', 'ndarray')
return nd_array is not None and inspect.isclass(type_object) and issubclass(type_object, nd_array)
def _to_str_no_trim(self, val):
return str(val.tolist()).replace('\n', ',').strip()
def get_str(self, val, do_trim=True):
if do_trim:
return get_value_repr(val)
try:
import numpy as np
with np.printoptions(threshold=sys.maxsize):
return self._to_str_no_trim(val)
except:
return self._to_str_no_trim(val)
class NdArrayItemsContainerProvider(object):
def can_provide(self, type_object, type_name):
return inspect.isclass(type_object) and issubclass(type_object, NdArrayItemsContainer)
def resolve(self, obj, attribute):
if attribute == '__len__':
return None
return obj.items[int(attribute)]
def get_dictionary(self, obj):
obj, offset = get_var_and_offset(obj)
l = len(obj.items)
d = OrderedDict()
format_str = '%0' + str(int(len(str(l)))) + 'd'
i = offset
for item in obj.items[offset:offset + MAX_ITEMS_TO_HANDLE]:
d[format_str % i] = item
i += 1
if i > MAX_ITEMS_TO_HANDLE + offset:
break
d['__len__'] = l
return d
import sys
if not sys.platform.startswith("java"):
TypeResolveProvider.register(NDArrayTypeResolveProvider)
if IS_PYCHARM:
TypeResolveProvider.register(NdArrayItemsContainerProvider)
StrPresentationProvider.register(NDArrayStrProvider)
@@ -1,5 +1,6 @@
import sys
from _pydevd_bundle.custom.pydevd_repr_utils import get_value_repr
from _pydevd_bundle.pydevd_constants import PANDAS_MAX_ROWS, PANDAS_MAX_COLS, PANDAS_MAX_COLWIDTH
from _pydevd_bundle.pydevd_extension_api import TypeResolveProvider, StrPresentationProvider
from _pydevd_bundle.pydevd_resolver import inspect, MethodWrapperType
@@ -88,7 +89,7 @@ class PandasDataFrameTypeResolveProvider(object):
replacements = {
# This actually calls: DataFrame.transpose(), which can be expensive, so,
# let's just add some string representation for it.
"T": "<transposed dataframe -- debugger:skipped eval>",
# "T": "<transposed dataframe -- debugger:skipped eval>",
# This creates a whole new dict{index: Series) for each column. Doing a
# subsequent repr() from this dict can be very slow, so, don't return it.
"_series": "<dict[index:Series] -- debugger:skipped eval>",
@@ -109,9 +110,18 @@ class PandasDataFrameTypeResolveProvider(object):
return repr(df)
return self.get_str(df)
def get_str(self, df):
with customize_pandas_options():
return repr(df)
def _to_str_no_trim(self, val):
return str(val.tolist()).replace('\n', ',').strip()
def get_str(self, val, do_trim=True):
if do_trim:
return get_value_repr(val)
try:
import numpy as np
with np.printoptions(threshold=sys.maxsize):
return self._to_str_no_trim(val)
except:
return self._to_str_no_trim(val)
class PandasSeriesTypeResolveProvider(object):
@@ -126,7 +136,7 @@ class PandasSeriesTypeResolveProvider(object):
replacements = {
# This actually calls: DataFrame.transpose(), which can be expensive, so,
# let's just add some string representation for it.
"T": "<transposed dataframe -- debugger:skipped eval>",
# "T": "<transposed dataframe -- debugger:skipped eval>",
# This creates a whole new dict{index: Series) for each column. Doing a
# subsequent repr() from this dict can be very slow, so, don't return it.
"_series": "<dict[index:Series] -- debugger:skipped eval>",
@@ -147,9 +157,18 @@ class PandasSeriesTypeResolveProvider(object):
return repr(df)
return self.get_str(df)
def get_str(self, series):
with customize_pandas_options():
return repr(series)
def _to_str_no_trim(self, val):
return str(val.tolist()).replace('\n', ',').strip()
def get_str(self, val, do_trim=True):
if do_trim:
return get_value_repr(val)
try:
import numpy as np
with np.printoptions(threshold=sys.maxsize):
return self._to_str_no_trim(val)
except:
return self._to_str_no_trim(val)
class PandasStylerTypeResolveProvider(object):
@@ -0,0 +1,21 @@
from typing import Any
from _pydevd_bundle.custom.pydevd_repr_utils import get_value_repr
from _pydevd_bundle.pydevd_extension_api import StrPresentationProvider
from pydevd_plugins.extensions.types.pydevd_plugin_numpy_types import NDArrayStrProvider
from pydevd_plugins.extensions.types.pydevd_plugins_django_form_str import DjangoFormStr
class PydevdReprStrProvider(StrPresentationProvider):
def can_provide(self, type_object, type_name):
# this provider can resolve anything that is not otherwise custom handled by our
# other custom resolvers, such as NDArrayStrProvider
return (not NDArrayStrProvider.can_provide(self, type_object, type_name)
and not DjangoFormStr.can_provide(self, type_object, type_name))
def get_str_in_context(self, val: Any, context: str):
return self.get_str(val)
def get_str(self, val, do_trim=True):
return get_value_repr(val)