resolve generic collections

This commit is contained in:
Philipp A. 2025-09-28 15:35:18 +02:00
parent 6cbdf8767e
commit 8fba4ebc2b
3 changed files with 83 additions and 124 deletions

View file

@ -238,9 +238,9 @@ def _does_obj_repr_evaluate_to_obj(obj):
# =======================================================================================================================
# DictResolver
# MappingResolver
# =======================================================================================================================
class DictResolver:
class MappingResolver:
sort_keys = not IS_PY36_OR_GREATER
def resolve(self, dct, key):
@ -452,7 +452,7 @@ class ForwardInternalResolverToObject:
return var.resolve(attribute)
class TupleResolver: # to enumerate tuples and lists
class SequenceResolver:
def resolve(self, var, attribute):
"""
:param var: that's the original object we're dealing with.
@ -657,7 +657,7 @@ class JyArrayResolver:
# =======================================================================================================================
# MultiValueDictResolver
# =======================================================================================================================
class MultiValueDictResolver(DictResolver):
class MultiValueDictResolver(MappingResolver):
def resolve(self, dct, key):
if key in (GENERATED_LEN_ATTR_NAME, TOO_LARGE_ATTR):
return None
@ -699,9 +699,9 @@ class DjangoFormResolver(DefaultResolver):
# =======================================================================================================================
# DequeResolver
# =======================================================================================================================
class DequeResolver(TupleResolver):
class DequeResolver(SequenceResolver):
def get_dictionary(self, var):
d = TupleResolver.get_dictionary(self, var)
d = SequenceResolver.get_dictionary(self, var)
d["maxlen"] = getattr(var, "maxlen", None)
return d
@ -709,7 +709,7 @@ class DequeResolver(TupleResolver):
# =======================================================================================================================
# OrderedDictResolver
# =======================================================================================================================
class OrderedDictResolver(DictResolver):
class OrderedDictResolver(MappingResolver):
sort_keys = False
def init_dict(self):
@ -765,8 +765,8 @@ class FrameResolver:
defaultResolver = DefaultResolver()
dictResolver = DictResolver()
tupleResolver = TupleResolver()
mappingResolver = MappingResolver()
sequenceResolver = SequenceResolver()
instanceResolver = InstanceResolver()
jyArrayResolver = JyArrayResolver()
setResolver = SetResolver()

View file

@ -1,7 +1,13 @@
import sys
from collections import deque, OrderedDict
from collections.abc import Mapping, Sequence, Set as AbstractSet
from types import FrameType
from typing import Optional
from _pydev_bundle import pydev_log
from _pydevd_bundle import pydevd_extension_utils
from _pydevd_bundle import pydevd_resolver
import sys
from _pydevd_bundle.pydevd_constants import (
BUILTINS_MODULE_NAME,
MAXIMUM_VARIABLE_REPRESENTATION_SIZE,
@ -13,14 +19,6 @@ from _pydev_bundle.pydev_imports import quote
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
frame_type = types.FrameType
except:
frame_type = None
def make_valid_xml_value(s):
@ -40,97 +38,47 @@ _IS_JYTHON = sys.platform.startswith("java")
def _create_default_type_map():
default_type_map = [
# None means that it should not be treated as a compound variable
# isintance does not accept a tuple on some versions of python, so, we must declare it expanded
(
type(None),
None,
),
(int, None),
(float, None),
(complex, None),
(str, None),
(tuple, pydevd_resolver.tupleResolver),
(list, pydevd_resolver.tupleResolver),
(dict, pydevd_resolver.dictResolver),
# non-compound types
((type(None), int, float, complex, str), None),
# collections
(Sequence, pydevd_resolver.sequenceResolver),
(deque, pydevd_resolver.dequeResolver),
(OrderedDict, pydevd_resolver.orderedDictResolver),
(Mapping, pydevd_resolver.mappingResolver),
(AbstractSet, pydevd_resolver.setResolver),
# other builtin types
(FrameType, pydevd_resolver.frameResolver),
# pydevd types
(DAPGrouper, pydevd_resolver.dapGrouperResolver),
((MoreItems, MoreItemsRange), pydevd_resolver.forwardInternalResolverToObject),
]
try:
from collections import OrderedDict
default_type_map.insert(0, (OrderedDict, pydevd_resolver.orderedDictResolver))
# we should put it before dict
except:
pass
try:
default_type_map.append((long, None)) # @UndefinedVariable
except:
pass # not available on all python versions
default_type_map.append((DAPGrouper, pydevd_resolver.dapGrouperResolver))
default_type_map.append((MoreItems, pydevd_resolver.forwardInternalResolverToObject))
default_type_map.append((MoreItemsRange, pydevd_resolver.forwardInternalResolverToObject))
try:
default_type_map.append((set, pydevd_resolver.setResolver))
except:
pass # not available on all python versions
try:
default_type_map.append((frozenset, pydevd_resolver.setResolver))
except:
pass # not available on all python versions
try:
from django.utils.datastructures import MultiValueDict
from django.forms import BaseForm
except ImportError:
pass # django may not be installed
else:
default_type_map.insert(0, (MultiValueDict, pydevd_resolver.multiValueDictResolver))
# we should put it before dict
except:
pass # django may not be installed
try:
from django.forms import BaseForm
default_type_map.insert(0, (BaseForm, pydevd_resolver.djangoFormResolver))
# we should put it before instance resolver
except:
pass # django may not be installed
try:
from collections import deque
default_type_map.append((deque, pydevd_resolver.dequeResolver))
except:
pass
try:
from ctypes import Array
default_type_map.append((Array, pydevd_resolver.tupleResolver))
except:
pass
if frame_type is not None:
default_type_map.append((frame_type, pydevd_resolver.frameResolver))
except ImportError:
pass # TODO: comment on reason why this might this not be available
else:
default_type_map.append((Array, pydevd_resolver.sequenceResolver))
if _IS_JYTHON:
from org.python import core # @UnresolvedImport
default_type_map.append((core.PyNone, None))
default_type_map.append((core.PyInteger, None))
default_type_map.append((core.PyLong, None))
default_type_map.append((core.PyFloat, None))
default_type_map.append((core.PyComplex, None))
default_type_map.append((core.PyString, None))
default_type_map.append((core.PyTuple, pydevd_resolver.tupleResolver))
default_type_map.append((core.PyList, pydevd_resolver.tupleResolver))
default_type_map.append((core.PyDictionary, pydevd_resolver.dictResolver))
default_type_map.append((core.PyStringMap, pydevd_resolver.dictResolver))
if hasattr(core, "PyJavaInstance"):
# Jython 2.5b3 removed it.
default_type_map.append((core.PyJavaInstance, pydevd_resolver.instanceResolver))
default_type_map += [
((core.PyNone, core.PyInteger, core.PyLong, core.PyFloat, core.PyComplex, core.PyString), None),
((core.PyTuple, core.PyList), pydevd_resolver.sequenceResolver),
((core.PyDictionary, core.PyStringMap), pydevd_resolver.mappingResolver),
]
return default_type_map

View file

@ -1,7 +1,16 @@
from __future__ import annotations
import sys
from types import MappingProxyType
from typing import TYPE_CHECKING
from collections import UserDict, UserList
import pytest
from _pydevd_bundle.pydevd_constants import IS_PY36_OR_GREATER, GENERATED_LEN_ATTR_NAME
from _pydevd_bundle import pydevd_constants, pydevd_frame_utils
import pytest
import sys
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
def check_len_entry(len_entry, first_2_params):
@ -10,12 +19,13 @@ def check_len_entry(len_entry, first_2_params):
assert len_entry[2]("check") == "len(check)"
def test_dict_resolver():
from _pydevd_bundle.pydevd_resolver import DictResolver
@pytest.mark.parametrize("map_cls", [dict, MappingProxyType, UserDict])
def test_mapping_resolver(map_cls: type[Mapping]):
from _pydevd_bundle.pydevd_resolver import MappingResolver
dict_resolver = DictResolver()
dct = {(1, 2): 2, "22": 22}
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(dict_resolver.get_contents_debug_adapter_protocol(dct))
mapping_resolver = MappingResolver()
dct = map_cls({(1, 2): 2, "22": 22})
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(mapping_resolver.get_contents_debug_adapter_protocol(dct))
len_entry = contents_debug_adapter_protocol.pop(-1)
check_len_entry(len_entry, (GENERATED_LEN_ATTR_NAME, 2))
if IS_PY36_OR_GREATER:
@ -25,13 +35,13 @@ def test_dict_resolver():
assert contents_debug_adapter_protocol == [("'22'", 22, "['22']"), ("(1, 2)", 2, "[(1, 2)]")]
def test_dict_resolver_hex():
from _pydevd_bundle.pydevd_resolver import DictResolver
def test_mapping_resolver_hex():
from _pydevd_bundle.pydevd_resolver import MappingResolver
dict_resolver = DictResolver()
mapping_resolver = MappingResolver()
dct = {(1, 10, 100): (10000, 100000, 100000)}
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(
dict_resolver.get_contents_debug_adapter_protocol(dct, fmt={"hex": True})
mapping_resolver.get_contents_debug_adapter_protocol(dct, fmt={"hex": True})
)
len_entry = contents_debug_adapter_protocol.pop(-1)
check_len_entry(len_entry, (GENERATED_LEN_ATTR_NAME, 1))
@ -164,13 +174,14 @@ def clear_contents_dictionary(dictionary):
return dictionary
def test_tuple_resolver():
from _pydevd_bundle.pydevd_resolver import TupleResolver
@pytest.mark.parametrize("seq_cls", [list, tuple, UserList])
def test_sequence_resolver(seq_cls: type[Sequence]):
from _pydevd_bundle.pydevd_resolver import SequenceResolver
tuple_resolver = TupleResolver()
seq_resolver = SequenceResolver()
fmt = {"hex": True}
lst = tuple(range(11))
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(tuple_resolver.get_contents_debug_adapter_protocol(lst))
lst = seq_cls(range(11))
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(seq_resolver.get_contents_debug_adapter_protocol(lst))
len_entry = contents_debug_adapter_protocol.pop(-1)
assert contents_debug_adapter_protocol == [
("00", 0, "[0]"),
@ -187,7 +198,7 @@ def test_tuple_resolver():
]
check_len_entry(len_entry, (GENERATED_LEN_ATTR_NAME, 11))
assert clear_contents_dictionary(tuple_resolver.get_dictionary(lst)) == {
assert clear_contents_dictionary(seq_resolver.get_dictionary(lst)) == {
"00": 0,
"01": 1,
"02": 2,
@ -202,9 +213,9 @@ def test_tuple_resolver():
GENERATED_LEN_ATTR_NAME: 11,
}
lst = tuple(range(17))
lst = seq_cls(range(17))
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(
tuple_resolver.get_contents_debug_adapter_protocol(lst, fmt=fmt)
seq_resolver.get_contents_debug_adapter_protocol(lst, fmt=fmt)
)
len_entry = contents_debug_adapter_protocol.pop(-1)
assert contents_debug_adapter_protocol == [
@ -228,7 +239,7 @@ def test_tuple_resolver():
]
check_len_entry(len_entry, (GENERATED_LEN_ATTR_NAME, 17))
assert clear_contents_dictionary(tuple_resolver.get_dictionary(lst, fmt=fmt)) == {
assert clear_contents_dictionary(seq_resolver.get_dictionary(lst, fmt=fmt)) == {
"0x00": 0,
"0x01": 1,
"0x02": 2,
@ -249,8 +260,8 @@ def test_tuple_resolver():
GENERATED_LEN_ATTR_NAME: 17,
}
lst = tuple(range(10))
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(tuple_resolver.get_contents_debug_adapter_protocol(lst))
lst = seq_cls(range(10))
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(seq_resolver.get_contents_debug_adapter_protocol(lst))
len_entry = contents_debug_adapter_protocol.pop(-1)
assert contents_debug_adapter_protocol == [
("0", 0, "[0]"),
@ -266,7 +277,7 @@ def test_tuple_resolver():
]
check_len_entry(len_entry, (GENERATED_LEN_ATTR_NAME, 10))
assert clear_contents_dictionary(tuple_resolver.get_dictionary(lst)) == {
assert clear_contents_dictionary(seq_resolver.get_dictionary(lst)) == {
"0": 0,
"1": 1,
"2": 2,
@ -281,7 +292,7 @@ def test_tuple_resolver():
}
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(
tuple_resolver.get_contents_debug_adapter_protocol(lst, fmt=fmt)
seq_resolver.get_contents_debug_adapter_protocol(lst, fmt=fmt)
)
len_entry = contents_debug_adapter_protocol.pop(-1)
assert contents_debug_adapter_protocol == [
@ -298,7 +309,7 @@ def test_tuple_resolver():
]
check_len_entry(len_entry, (GENERATED_LEN_ATTR_NAME, 10))
assert clear_contents_dictionary(tuple_resolver.get_dictionary(lst, fmt=fmt)) == {
assert clear_contents_dictionary(seq_resolver.get_dictionary(lst, fmt=fmt)) == {
"0x0": 0,
"0x1": 1,
"0x2": 2,
@ -313,17 +324,17 @@ def test_tuple_resolver():
}
def test_tuple_resolver_mixed():
from _pydevd_bundle.pydevd_resolver import TupleResolver
def test_sequence_resolver_mixed():
from _pydevd_bundle.pydevd_resolver import SequenceResolver
tuple_resolver = TupleResolver()
seq_resolver = SequenceResolver()
class CustomTuple(tuple):
pass
my_tuple = CustomTuple([1, 2])
my_tuple.some_value = 10
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(tuple_resolver.get_contents_debug_adapter_protocol(my_tuple))
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(seq_resolver.get_contents_debug_adapter_protocol(my_tuple))
len_entry = contents_debug_adapter_protocol.pop(-1)
check_len_entry(len_entry, (GENERATED_LEN_ATTR_NAME, 2))
assert contents_debug_adapter_protocol == [
@ -333,7 +344,7 @@ def test_tuple_resolver_mixed():
]
def test_tuple_resolver_ctypes():
def test_sequence_resolver_ctypes():
import ctypes
from _pydevd_bundle.pydevd_xml import get_type