From 79b7c5634ed5179d193ae563c56ee25dcaed9c30 Mon Sep 17 00:00:00 2001 From: Fabio Zadrozny Date: Fri, 2 Dec 2022 09:08:23 -0300 Subject: [PATCH] Don't apply pandas customizations when value is requested for clipboard or repl. Fixes #1078 --- .../pydevd/_pydevd_bundle/pydevd_comm.py | 2 +- .../_pydevd_bundle/pydevd_extension_api.py | 49 +++++++++++++------ .../_pydevd_bundle/pydevd_suspended_frames.py | 12 ++++- .../pydevd/_pydevd_bundle/pydevd_xml.py | 27 ++++++++-- .../types/pydevd_plugin_pandas_types.py | 26 ++++++++++ .../pydevd/tests_python/test_debugger_json.py | 5 ++ 6 files changed, 99 insertions(+), 22 deletions(-) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py index a6b8a0ee9..e1e93b7ee 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py @@ -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'], diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_extension_api.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_extension_api.py index aac7c799d..8c5a441b1 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_extension_api.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_extension_api.py @@ -1,4 +1,5 @@ import abc +from typing import Any # borrowed from from six @@ -6,6 +7,7 @@ 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) @@ -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 """ @@ -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 diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py index 955ba9a19..34c404d0e 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py @@ -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): @@ -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() @@ -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') diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py index c235086b6..5d1ed0fd7 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py @@ -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 @@ -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() @@ -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,)) @@ -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 @@ -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 diff --git a/src/debugpy/_vendored/pydevd/pydevd_plugins/extensions/types/pydevd_plugin_pandas_types.py b/src/debugpy/_vendored/pydevd/pydevd_plugins/extensions/types/pydevd_plugin_pandas_types.py index 2c1090cf8..abddc27bc 100644 --- a/src/debugpy/_vendored/pydevd/pydevd_plugins/extensions/types/pydevd_plugin_pandas_types.py +++ b/src/debugpy/_vendored/pydevd/pydevd_plugins/extensions/types/pydevd_plugin_pandas_types.py @@ -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) @@ -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) diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py index 125fc12dd..85d97af09 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py @@ -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