Merge pull request #96 from fabioz/ptvsd_998_dict_order

Keep order of items in dictionary (Py3.6 onwards) or odict.
This commit is contained in:
Karthik Nadig 2020-04-01 18:32:27 -07:00 committed by GitHub
commit cd803cc14b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 156 additions and 43 deletions

View file

@ -8,7 +8,7 @@ import traceback
from os.path import basename
from functools import partial
from _pydevd_bundle.pydevd_constants import dict_iter_items, dict_keys, xrange
from _pydevd_bundle.pydevd_constants import dict_iter_items, dict_keys, xrange, IS_PY36_OR_GREATER
from _pydevd_bundle.pydevd_safe_repr import SafeRepr
# Note: 300 is already a lot to see in the outline (after that the user should really use the shell to get things)
@ -241,6 +241,8 @@ class DefaultResolver:
#=======================================================================================================================
class DictResolver:
sort_keys = not IS_PY36_OR_GREATER
def resolve(self, dict, key):
if key in ('__len__', TOO_LARGE_ATTR):
return None
@ -302,6 +304,9 @@ class DictResolver:
if from_default_resolver:
ret = from_default_resolver + ret
if not self.sort_keys:
return ret
return sorted(ret, key=lambda tup: sorted_attributes_key(tup[0]))
def get_dictionary(self, dict):
@ -580,6 +585,8 @@ class DequeResolver(TupleResolver):
#=======================================================================================================================
class OrderedDictResolver(DictResolver):
sort_keys = False
def init_dict(self):
return OrderedDict()

View file

@ -45,6 +45,13 @@ def _create_default_type_map():
(list, pydevd_resolver.tupleResolver),
(dict, pydevd_resolver.dictResolver),
]
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:

View file

@ -0,0 +1,14 @@
def check():
from collections import OrderedDict
import sys
# On 3.6 onwards, use a regular dict.
odict = OrderedDict() if sys.version_info[:2] < (3, 6) else {}
odict[4] = 'first'
odict[3] = 'second'
odict[2] = 'last'
print('break here')
if __name__ == '__main__':
check()
print('TEST SUCEEDED')

View file

@ -16,7 +16,8 @@ from _pydevd_bundle._debug_adapter.pydevd_schema import (ThreadEvent, ModuleEven
InitializeRequestArguments, TerminateArguments, TerminateRequest, TerminatedEvent)
from _pydevd_bundle.pydevd_comm_constants import file_system_encoding
from _pydevd_bundle.pydevd_constants import (int_types, IS_64BIT_PROCESS,
PY_VERSION_STR, PY_IMPL_VERSION_STR, PY_IMPL_NAME, IS_PY36_OR_GREATER, IS_PY39_OR_GREATER)
PY_VERSION_STR, PY_IMPL_VERSION_STR, PY_IMPL_NAME, IS_PY36_OR_GREATER, IS_PY39_OR_GREATER,
IS_PY37_OR_GREATER)
from tests_python import debugger_unittest
from tests_python.debug_constants import TEST_CHERRYPY, IS_PY2, TEST_DJANGO, TEST_FLASK, IS_PY26, \
IS_PY27, IS_CPYTHON, TEST_GEVENT
@ -1177,6 +1178,38 @@ def test_modules(case_setup):
writer.finished_ok = True
@pytest.mark.skipif(IS_PY26, reason='Python 2.6 does not have an ordered dict')
def test_dict_ordered(case_setup):
with case_setup.test_file('_debugger_case_odict.py') as writer:
json_facade = JsonFacade(writer)
json_facade.write_set_breakpoints(writer.get_line_index_with_content('break here'))
json_facade.write_make_initial_run()
json_hit = json_facade.wait_for_thread_stopped()
json_hit = json_facade.get_stack_as_json_hit(json_hit.thread_id)
variables_response = json_facade.get_variables_response(json_hit.frame_id)
variables_references = variables_response.body.variables
for dct in variables_references:
if dct['name'] == 'odict':
break
else:
raise AssertionError('Expected to find "odict".')
ref = dct['variablesReference']
assert isinstance(ref, int_types)
# : :type variables_response: VariablesResponse
variables_response = json_facade.get_variables_response(ref)
assert [(d['name'], d['value']) for d in variables_response.body.variables if not d['name'].startswith('_OrderedDict')] == [
('4', "'first'"), ('3', "'second'"), ('2', "'last'"), ('__len__', '3')]
json_facade.write_continue()
writer.finished_ok = True
@pytest.mark.skipif(IS_JYTHON, reason='Putting unicode on frame vars does not work on Jython.')
def test_stack_and_variables_dict(case_setup):
with case_setup.test_file('_debugger_case_local_variables.py') as writer:

View file

@ -1,4 +1,5 @@
from tests_python.debug_constants import IS_PY2
from _pydevd_bundle.pydevd_constants import IS_PY36_OR_GREATER
def check_len_entry(len_entry, first_2_params):
@ -14,7 +15,11 @@ def test_dict_resolver():
contents_debug_adapter_protocol = dict_resolver.get_contents_debug_adapter_protocol(dct)
len_entry = contents_debug_adapter_protocol.pop(-1)
check_len_entry(len_entry, ('__len__', 2))
if IS_PY2:
if IS_PY36_OR_GREATER:
assert contents_debug_adapter_protocol == [
('(1, 2)', 2, '[(1, 2)]'), ("'22'", 22, "['22']")]
elif IS_PY2:
assert contents_debug_adapter_protocol == [
('(1, 2)', 2, '[(1, 2)]'), (u"u'22'", 22, u"[u'22']")]
else:

View file

@ -166,7 +166,10 @@ def test_variable_sort(pyfile, target, run):
"variables", {"variablesReference": b_test["variablesReference"]}
)["variables"]
var_names = [v["name"] for v in b_test_vars]
assert var_names == ["'abcd'", "'eggs'", "'spam'", "__len__"]
if sys.version_info[:2] >= (3, 6):
assert var_names == ["'spam'", "'eggs'", "'abcd'", "__len__"]
else:
assert var_names == ["'abcd'", "'eggs'", "'spam'", "__len__"]
# Numeric dict keys must be sorted as numbers.
if not "https://github.com/microsoft/ptvsd/issues/213":
@ -394,45 +397,89 @@ def test_hex_numbers(pyfile, target, run):
"variables",
{"variablesReference": c["variablesReference"], "format": {"hex": True}},
)["variables"]
assert c_vars == [
some.dict.containing(
{
"name": "0x3e8",
"value": "0x3e8",
"type": "int",
"evaluateName": "c[1000]",
"variablesReference": 0,
}
),
some.dict.containing(
{
"name": "0x64",
"value": "0x64",
"type": "int",
"evaluateName": "c[100]",
"variablesReference": 0,
}
),
some.dict.containing(
{
"name": "0xa",
"value": "0xa",
"type": "int",
"evaluateName": "c[10]",
"variablesReference": 0,
}
),
some.dict.containing(
{
"name": "__len__",
"value": "0x3",
"type": "int",
"evaluateName": "len(c)",
"variablesReference": 0,
"presentationHint": {"attributes": ["readOnly"]},
}
),
]
if sys.version_info[:2] < (3, 6):
# Sorted dict keys by the name before Python 3.6.
assert c_vars == [
some.dict.containing(
{
"name": "0x3e8",
"value": "0x3e8",
"type": "int",
"evaluateName": "c[1000]",
"variablesReference": 0,
}
),
some.dict.containing(
{
"name": "0x64",
"value": "0x64",
"type": "int",
"evaluateName": "c[100]",
"variablesReference": 0,
}
),
some.dict.containing(
{
"name": "0xa",
"value": "0xa",
"type": "int",
"evaluateName": "c[10]",
"variablesReference": 0,
}
),
some.dict.containing(
{
"name": "__len__",
"value": "0x3",
"type": "int",
"evaluateName": "len(c)",
"variablesReference": 0,
"presentationHint": {"attributes": ["readOnly"]},
}
),
]
else:
# Use dict sequence on Python 3.6 onwards.
assert c_vars == [
some.dict.containing(
{
"name": "0xa",
"value": "0xa",
"type": "int",
"evaluateName": "c[10]",
"variablesReference": 0,
}
),
some.dict.containing(
{
"name": "0x64",
"value": "0x64",
"type": "int",
"evaluateName": "c[100]",
"variablesReference": 0,
}
),
some.dict.containing(
{
"name": "0x3e8",
"value": "0x3e8",
"type": "int",
"evaluateName": "c[1000]",
"variablesReference": 0,
}
),
some.dict.containing(
{
"name": "__len__",
"value": "0x3",
"type": "int",
"evaluateName": "len(c)",
"variablesReference": 0,
"presentationHint": {"attributes": ["readOnly"]},
}
),
]
d_vars = session.request(
"variables",