Skip to content

Commit

Permalink
Don't apply pandas customizations when value is requested for clipboa…
Browse files Browse the repository at this point in the history
…rd or repl. Fixes microsoft#1078
  • Loading branch information
fabioz committed Dec 2, 2022
1 parent 9e94581 commit 79b7c56
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1225,7 +1225,7 @@ def __create_frame():

else:
variable = frame_tracker.obtain_as_variable(expression, eval_result, frame=frame)
var_data = variable.get_var_data(fmt=fmt, **safe_repr_custom_attrs)
var_data = variable.get_var_data(fmt=fmt, context=context, **safe_repr_custom_attrs)

body = pydevd_schema.EvaluateResponseBody(
result=var_data['value'],
Expand Down
49 changes: 35 additions & 14 deletions src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_extension_api.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import abc
from typing import Any


# borrowed from from six
def _with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""

class metaclass(meta):

def __new__(cls, name, this_bases, d):
return meta(name, bases, d)

Expand All @@ -17,49 +19,50 @@ def __new__(cls, name, this_bases, d):
# =======================================================================================================================
class _AbstractResolver(_with_metaclass(abc.ABCMeta)):
"""
This class exists only for documentation purposes to explain how to create a resolver.
This class exists only for documentation purposes to explain how to create a resolver.
Some examples on how to resolve things:
- list: get_dictionary could return a dict with index->item and use the index to resolve it later
- set: get_dictionary could return a dict with id(object)->object and reiterate in that array to resolve it later
- arbitrary instance: get_dictionary could return dict with attr_name->attr and use getattr to resolve it later
Some examples on how to resolve things:
- list: get_dictionary could return a dict with index->item and use the index to resolve it later
- set: get_dictionary could return a dict with id(object)->object and reiterate in that array to resolve it later
- arbitrary instance: get_dictionary could return dict with attr_name->attr and use getattr to resolve it later
"""

@abc.abstractmethod
def resolve(self, var, attribute):
"""
In this method, we'll resolve some child item given the string representation of the item in the key
representing the previously asked dictionary.
In this method, we'll resolve some child item given the string representation of the item in the key
representing the previously asked dictionary.
@param var: this is the actual variable to be resolved.
@param attribute: this is the string representation of a key previously returned in get_dictionary.
:param var: this is the actual variable to be resolved.
:param attribute: this is the string representation of a key previously returned in get_dictionary.
"""
raise NotImplementedError

@abc.abstractmethod
def get_dictionary(self, var):
"""
@param var: this is the variable that should have its children gotten.
:param var: this is the variable that should have its children gotten.
@return: a dictionary where each pair key, value should be shown to the user as children items
in the variables view for the given var.
:return: a dictionary where each pair key, value should be shown to the user as children items
in the variables view for the given var.
"""
raise NotImplementedError


class _AbstractProvider(_with_metaclass(abc.ABCMeta)):

@abc.abstractmethod
def can_provide(self, type_object, type_name):
raise NotImplementedError


# =======================================================================================================================
# API CLASSES:
# =======================================================================================================================


class TypeResolveProvider(_AbstractResolver, _AbstractProvider):
"""
Implement this in an extension to provide a custom resolver, see _AbstractResolver
Implement this in an extension to provide a custom resolver, see _AbstractResolver
"""


Expand All @@ -68,6 +71,24 @@ class StrPresentationProvider(_AbstractProvider):
Implement this in an extension to provide a str presentation for a type
"""

def get_str_in_context(self, val: Any, context: str):
'''
:param val:
This is the object for which we want a string representation.
:param context:
This is the context in which the variable is being requested. Valid values:
"watch",
"repl",
"hover",
"clipboard"
:note: this method is not required (if it's not available, get_str is called directly,
so, it's only needed if the string representation needs to be converted based on
the context).
'''
return self.get_str(val)

@abc.abstractmethod
def get_str(self, val):
raise NotImplementedError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from _pydev_bundle.pydev_imports import Exec
from _pydevd_bundle.pydevd_frame_utils import FramesList
from _pydevd_bundle.pydevd_utils import ScopeRequest, DAPGrouper, Timer
from typing import Optional


class _AbstractVariable(object):
Expand All @@ -35,10 +36,17 @@ def get_value(self):
def get_variable_reference(self):
return id(self.value)

def get_var_data(self, fmt=None, **safe_repr_custom_attrs):
def get_var_data(self, fmt: Optional[dict]=None, context: Optional[str]=None, **safe_repr_custom_attrs):
'''
:param dict fmt:
Format expected by the DAP (keys: 'hex': bool, 'rawString': bool)
:param context:
This is the context in which the variable is being requested. Valid values:
"watch",
"repl",
"hover",
"clipboard"
'''
timer = Timer()
safe_repr = SafeRepr()
Expand All @@ -49,7 +57,7 @@ def get_var_data(self, fmt=None, **safe_repr_custom_attrs):
setattr(safe_repr, key, val)

type_name, _type_qualifier, _is_exception_on_eval, resolver, value = get_variable_details(
self.value, to_string=safe_repr)
self.value, to_string=safe_repr, context=context)

is_raw_string = type_name in ('str', 'bytes', 'bytearray')

Expand Down
27 changes: 22 additions & 5 deletions src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from _pydevd_bundle.pydevd_extension_api import TypeResolveProvider, StrPresentationProvider
from _pydevd_bundle.pydevd_utils import isinstance_checked, hasattr_checked, DAPGrouper
from _pydevd_bundle.pydevd_resolver import get_var_scope, MoreItems, MoreItemsRange
from typing import Optional

try:
import types
Expand Down Expand Up @@ -205,14 +206,22 @@ def _get_type(self, o, type_object, type_name):

return self._base_get_type(o, type_object, type_name)

def str_from_providers(self, o, type_object, type_name):
def _get_str_from_provider(self, provider, o, context: Optional[str]=None):
if context is not None:
get_str_in_context = getattr(provider, 'get_str_in_context', None)
if get_str_in_context is not None:
return get_str_in_context(o, context)

return provider.get_str(o)

def str_from_providers(self, o, type_object, type_name, context: Optional[str]=None):
provider = self._type_to_str_provider_cache.get(type_object)

if provider is self.NO_PROVIDER:
return None

if provider is not None:
return provider.get_str(o)
return self._get_str_from_provider(provider, o, context)

if not self._initialized:
self._initialize()
Expand All @@ -221,7 +230,7 @@ def str_from_providers(self, o, type_object, type_name):
if provider.can_provide(type_object, type_name):
self._type_to_str_provider_cache[type_object] = provider
try:
return provider.get_str(o)
return self._get_str_from_provider(provider, o, context)
except:
pydev_log.exception("Error when getting str with custom provider: %s." % (provider,))

Expand Down Expand Up @@ -297,7 +306,15 @@ def frame_vars_to_xml(frame_f_locals, hidden_ns=None):
return ''.join(return_values_xml)


def get_variable_details(val, evaluate_full_value=True, to_string=None):
def get_variable_details(val, evaluate_full_value=True, to_string=None, context: Optional[str]=None):
'''
:param context:
This is the context in which the variable is being requested. Valid values:
"watch",
"repl",
"hover",
"clipboard"
'''
try:
# This should be faster than isinstance (but we have to protect against not having a '__class__' attribute).
is_exception_on_eval = val.__class__ == ExceptionOnEvaluate
Expand All @@ -315,7 +332,7 @@ def get_variable_details(val, evaluate_full_value=True, to_string=None):
value = DEFAULT_VALUE
else:
try:
str_from_provider = _str_from_providers(v, _type, type_name)
str_from_provider = _str_from_providers(v, _type, type_name, context)
if str_from_provider is not None:
value = str_from_provider

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,19 @@ def get_dictionary(self, obj):
}
return _get_dictionary(obj, replacements)

def get_str_in_context(self, df, context:str):
'''
:param context:
This is the context in which the variable is being requested. Valid values:
"watch",
"repl",
"hover",
"clipboard"
'''
if context in ('repl', 'clipboard'):
return repr(df)
return self.get_str(df)

def get_str(self, df):
with customize_pandas_options():
return repr(df)
Expand Down Expand Up @@ -127,6 +140,19 @@ def get_dictionary(self, obj):
}
return _get_dictionary(obj, replacements)

def get_str_in_context(self, df, context:str):
'''
:param context:
This is the context in which the variable is being requested. Valid values:
"watch",
"repl",
"hover",
"clipboard"
'''
if context in ('repl', 'clipboard'):
return repr(df)
return self.get_str(df)

def get_str(self, series):
with customize_pandas_options():
return repr(series)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6211,6 +6211,11 @@ def pandas_mod():
assert name_to_var['df'].value.count('\n') <= 63
assert '...' in name_to_var['df'].value

evaluate_response = json_facade.evaluate('df', json_hit.frame_id, context='repl')
evaluate_response_body = evaluate_response.body.to_dict()
assert '...' not in evaluate_response_body['result']
assert evaluate_response_body['result'].count('\n') > 4999

# Check the custom repr(Series)
assert name_to_var['series'].value.count('\n') <= 60
assert '...' in name_to_var['series'].value
Expand Down

0 comments on commit 79b7c56

Please sign in to comment.