From 25f3f43a21b0fa02c58fc563f0a8ad7db1f6c757 Mon Sep 17 00:00:00 2001 From: Maxim Popov Date: Fri, 19 Sep 2025 22:17:05 +0000 Subject: [PATCH] [debugger] PY-83906: enable shape/dtype evaluation for any type with shape/dtype attribute removes allowlist with data container types also fixes PY-83971 Merge-request: IJ-MR-176078 Merged-by: Maxim Popov GitOrigin-RevId: 101694635e693029e6da01f49080a3def98e48d4 --- .../pydev/_pydevd_bundle/pydevd_thrift.py | 10 +++--- .../pydev/_pydevd_bundle/pydevd_utils.py | 35 ++++--------------- .../pydev/_pydevd_bundle/pydevd_xml.py | 10 +++--- .../env/debug/tests/PythonDebuggerTest.java | 22 ------------ 4 files changed, 16 insertions(+), 61 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_thrift.py b/python/helpers/pydev/_pydevd_bundle/pydevd_thrift.py index dc5badccef69..7c0ae77ac3fb 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_thrift.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_thrift.py @@ -16,7 +16,7 @@ from _pydevd_bundle.pydevd_constants import dict_iter_items, dict_keys, IS_PY3K, from _pydevd_bundle.pydevd_extension_api import TypeResolveProvider, StrPresentationProvider from _pydevd_bundle.pydevd_user_type_renderers_utils import try_get_type_renderer_for_var from _pydevd_bundle.pydevd_utils import is_string, should_evaluate_full_value, \ - should_evaluate_shape, is_container_with_shape_dtype + should_evaluate_shape, has_attribute_safe from _pydevd_bundle.pydevd_vars import get_label, array_default_format, is_able_to_format_number, MAXIMUM_ARRAY_SIZE, \ get_column_formatter_by_type, get_formatted_row_elements, IAtPolarsAccessor, DEFAULT_DF_FORMAT, DATAFRAME_HEADER_LOAD_MAX_SIZE from pydev_console.pydev_protocol import DebugValue, GetArrayResponse, ArrayData, ArrayHeaders, ColHeader, RowHeader, \ @@ -334,10 +334,10 @@ def var_to_struct(val, name, format='%s', do_trim=True, evaluate_full_value=True # shape to struct try: - if should_evaluate_shape() and is_container_with_shape_dtype(type_qualifier, typeName, v): - if hasattr(v, 'shape') and not callable(v.shape): + if should_evaluate_shape(): + if has_attribute_safe(v, 'shape') and not callable(v.shape): debug_value.shape = str(tuple(v.shape)) - elif hasattr(v, '__len__') and not is_string(v): + elif has_attribute_safe(v, '__len__') and not is_string(v): debug_value.shape = str(len(v)) except: pass @@ -345,7 +345,7 @@ def var_to_struct(val, name, format='%s', do_trim=True, evaluate_full_value=True # data type info to xml (for arrays and tensors) debug_value.arrayElementType = '' try: - if is_container_with_shape_dtype(type_qualifier, typeName, v) and hasattr(v, 'dtype'): + if has_attribute_safe(v, 'shape') and has_attribute_safe(v, 'dtype'): debug_value.arrayElementType = str(v.dtype) except: pass diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py b/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py index fafbaac0b437..7058a87a5c1f 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py @@ -4,17 +4,6 @@ import ctypes import os import signal import traceback - -try: - import torch -except Exception: - pass - -try: - import tensorflow as tf -except Exception: - pass - import pydevd_file_utils try: @@ -616,24 +605,6 @@ def is_pandas_container(type_qualifier, var_type, var): def is_numpy_container(type_qualifier, var_type, var): return var_type == "ndarray" and type_qualifier == "numpy" and hasattr(var, "shape") -def is_pytorch_tensor(type_qualifier, var): - try: - import torch - return type_qualifier == "torch" and torch.is_tensor(var) - except ImportError: - return False # Can't be torch if it is not installed - -def is_tf_tensor(type_qualifier, var): - try: - import tensorflow as tf - return type_qualifier.startswith("tensorflow") and tf.is_tensor(var) - except ImportError: - return False # Can't be tensorflow if it is not installed - -def is_container_with_shape_dtype(type_qualifier, var_type, var): - return (is_numpy_container(type_qualifier, var_type, var) - or is_pytorch_tensor(type_qualifier, var) - or is_tf_tensor(type_qualifier, var)) def is_builtin(x): return getattr(x, '__module__', None) == BUILTINS_MODULE_NAME @@ -659,6 +630,12 @@ def should_evaluate_shape(): return LOAD_VALUES_POLICY != ValuesPolicy.ON_DEMAND +def has_attribute_safe(obj, attr_name): + """Evaluates the existence of attribute without accessing it.""" + attr = inspect.getattr_static(obj, attr_name, None) + return attr is not None + + def is_safe_to_access(obj, attr_name): """Evaluates the safety of attribute accessibility via `obj.attr_name`. diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py b/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py index 7ac0de8f0042..266775c10a49 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py @@ -21,7 +21,7 @@ from _pydevd_bundle.pydevd_repr_utils import get_value_repr from _pydevd_bundle.pydevd_user_type_renderers_utils import \ try_get_type_renderer_for_var from _pydevd_bundle.pydevd_utils import is_string, should_evaluate_full_value, \ - should_evaluate_shape, is_safe_to_access, is_container_with_shape_dtype + should_evaluate_shape, has_attribute_safe try: import types @@ -344,10 +344,10 @@ def var_to_xml(val, name, do_trim=True, additional_in_xml='', evaluate_full_valu # shape to xml xml_shape = '' try: - if should_evaluate_shape() and is_container_with_shape_dtype(type_qualifier, typeName, v): - if hasattr(v, 'shape') and not callable(v.shape): + if should_evaluate_shape(): + if has_attribute_safe(v, 'shape') and not callable(v.shape): xml_shape = ' shape="%s"' % make_valid_xml_value(str(tuple(v.shape))) - elif hasattr(v, '__len__') and not is_string(v): + elif has_attribute_safe(v, '__len__') and not is_string(v): xml_shape = ' shape="%s"' % make_valid_xml_value("%s" % str(len(v))) except: pass @@ -356,7 +356,7 @@ def var_to_xml(val, name, do_trim=True, additional_in_xml='', evaluate_full_valu # we use it for view as image xml_data_type = '' try: - if is_container_with_shape_dtype(type_qualifier, typeName, v): + if has_attribute_safe(v, "dtype"): xml_data_type = ' arrayElementType="%s"' % make_valid_xml_value(str(v.dtype)) except: pass diff --git a/python/testSrc/com/jetbrains/env/debug/tests/PythonDebuggerTest.java b/python/testSrc/com/jetbrains/env/debug/tests/PythonDebuggerTest.java index ab3e21049944..a3a14c7dbfab 100644 --- a/python/testSrc/com/jetbrains/env/debug/tests/PythonDebuggerTest.java +++ b/python/testSrc/com/jetbrains/env/debug/tests/PythonDebuggerTest.java @@ -761,26 +761,4 @@ public class PythonDebuggerTest extends PyEnvTestCase { } }); } - - @Test - public void testPropertyAccess() { - runPythonTest(new PyDebuggerTask("/debug", "test_property_output.py") { - - @Override - public void before() { toggleBreakpoint(getScriptName(), 11); } - - @Override - public void testing() throws Exception { - waitForPause(); - assertFalse("Output shouldn't contain unexpected prints from @property", output().contains("called property")); - resume(); - waitForTerminate(); - } - - @Override - public boolean isLanguageLevelSupported(@NotNull final LanguageLevel level) { - return level.compareTo(LanguageLevel.PYTHON27) > 0; - } - }); - } }