Skip to content

Commit 2c707ac

Browse files
authored
Fix duplicate extraction of from_dict (#204)
* Fix duplicate extraction of from_dict * Update tests * Remove unwanted code
1 parent 8be4f70 commit 2c707ac

File tree

2 files changed

+60
-11
lines changed

2 files changed

+60
-11
lines changed

msrest/serialization.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,8 @@ def from_dict(cls, data, key_extractors=None, content_type=None):
336336
"""
337337
deserializer = Deserializer(cls._infer_class_models())
338338
deserializer.key_extractors = [
339-
rest_key_case_insensitive_extractor,
340339
attribute_key_case_insensitive_extractor,
340+
rest_key_case_insensitive_extractor,
341341
last_rest_key_case_insensitive_extractor
342342
] if key_extractors is None else key_extractors
343343
return deserializer(cls.__name__, data, content_type=content_type)
@@ -1160,11 +1160,17 @@ def rest_key_case_insensitive_extractor(attr, attr_desc, data):
11601160
return attribute_key_case_insensitive_extractor(key, None, working_data)
11611161

11621162
def last_rest_key_extractor(attr, attr_desc, data):
1163+
"""Extract the attribute in "data" based on the last part of the JSON path key.
1164+
"""
11631165
key = attr_desc['key']
11641166
dict_keys = _FLATTEN.split(key)
11651167
return attribute_key_extractor(dict_keys[-1], None, data)
11661168

11671169
def last_rest_key_case_insensitive_extractor(attr, attr_desc, data):
1170+
"""Extract the attribute in "data" based on the last part of the JSON path key.
1171+
1172+
This is the case insensitive version of "last_rest_key_extractor"
1173+
"""
11681174
key = attr_desc['key']
11691175
dict_keys = _FLATTEN.split(key)
11701176
return attribute_key_case_insensitive_extractor(dict_keys[-1], None, data)
@@ -1259,8 +1265,8 @@ def xml_key_extractor(attr, attr_desc, data):
12591265
class Deserializer(object):
12601266
"""Response object model deserializer.
12611267
1262-
:param dict classes: Class type dictionary for deserializing
1263-
complex types.
1268+
:param dict classes: Class type dictionary for deserializing complex types.
1269+
:ivar list key_extractors: Ordered list of extractors to be used by this deserializer.
12641270
"""
12651271

12661272
basic_types = {str: 'str', int: 'int', bool: 'bool', float: 'float'}
@@ -1375,7 +1381,15 @@ def _deserialize(self, target_obj, data):
13751381
found_value = key_extractor(attr, attr_desc, data)
13761382
if found_value is not None:
13771383
if raw_value is not None and raw_value != found_value:
1378-
raise KeyError('Use twice the key: "{}"'.format(attr))
1384+
msg = ("Ignoring extracted value '%s' from %s for key '%s'"
1385+
" (duplicate extraction, follow extractors order)" )
1386+
_LOGGER.warning(
1387+
msg,
1388+
found_value,
1389+
key_extractor,
1390+
attr
1391+
)
1392+
continue
13791393
raw_value = found_value
13801394

13811395
value = self.deserialize_data(raw_value, attr_desc['type'])

tests/test_serialization.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,33 @@ def assert_model(inst):
14361436
self.TestObj.from_dict(attr_data)
14371437
assert_model(model_instance)
14381438

1439+
def test_twice_key_scenario(self):
1440+
# Te reproduce the initial bug, you need a attribute named after the last part
1441+
# of a flattening JSON from another attribute (here type)
1442+
# https://github.com/Azure/azure-sdk-for-python/issues/11422
1443+
# Issue happend where searching for "type2", since we found a match in both "type2" and "type" keys
1444+
1445+
class LocalModel(Model):
1446+
_attribute_map = {
1447+
'id': {'key': 'id', 'type': 'int'},
1448+
'type': {'key': 'type_dont_matter_not_used', 'type': 'str'},
1449+
'type2': {'key': 'properties.type', 'type': 'str'},
1450+
}
1451+
1452+
def __init__(self, **kwargs):
1453+
super(LocalModel, self).__init__(**kwargs)
1454+
1455+
raw = {
1456+
'id': 42,
1457+
'type': "type",
1458+
'type2': "type2"
1459+
}
1460+
1461+
m = LocalModel.from_dict(raw)
1462+
assert m.id == 42
1463+
assert m.type == "type"
1464+
assert m.type2 == "type2"
1465+
14391466
def test_array_deserialize(self):
14401467
result = self.d('[str]', ["a","b"])
14411468
assert result == ['a','b']
@@ -1503,13 +1530,21 @@ class TestKeyTypeObj(Model):
15031530
self.assertEqual(3, obj.attr_c)
15041531
self.assertEqual(4, obj.attr_d)
15051532

1506-
with self.assertRaises(DeserializationError):
1507-
obj = TestKeyTypeObj.from_dict({
1508-
"attr_b": 1,
1509-
"id": 2,
1510-
"keyc": 3,
1511-
"keyd": 4
1512-
})
1533+
# This one used to raise an exception, but after https://github.com/Azure/msrest-for-python/pull/204
1534+
# we decide to accept it with log warning
1535+
1536+
obj = TestKeyTypeObj.from_dict({
1537+
"attr_a": 1,
1538+
"attr_b": 12, # Conflict with "id"
1539+
"id": 14, # Conflict with "attr_b"
1540+
"keyc": 3,
1541+
"keyd": 4
1542+
})
1543+
1544+
self.assertEqual(1, obj.attr_a)
1545+
self.assertEqual(12, obj.attr_b) # from_dict will prioritize attribute syntax
1546+
self.assertEqual(3, obj.attr_c)
1547+
self.assertEqual(4, obj.attr_d)
15131548

15141549
def test_basic_deserialization(self):
15151550
class TestObj(Model):

0 commit comments

Comments
 (0)