Skip to content

Commit 78d9329

Browse files
authored
Merge pull request #12 from Azure/polymorphic_from_json
Fix #11 - Allow polymorphic serialization from JSON like objects
2 parents d6d55ee + 010d399 commit 78d9329

File tree

2 files changed

+176
-73
lines changed

2 files changed

+176
-73
lines changed

msrest/serialization.py

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -113,31 +113,38 @@ def _get_subtype_map(cls):
113113
return base._subtype_map
114114
return {}
115115

116+
@classmethod
117+
def _flatten_subtype(cls, key, objects):
118+
if not '_subtype_map' in cls.__dict__:
119+
return {}
120+
result = dict(cls._subtype_map[key])
121+
for valuetype in cls._subtype_map[key].values():
122+
result.update(objects[valuetype]._flatten_subtype(key, objects))
123+
return result
124+
116125
@classmethod
117126
def _classify(cls, response, objects):
118127
"""Check the class _subtype_map for any child classes.
119-
We want to ignore any inheirited _subtype_maps.
128+
We want to ignore any inherited _subtype_maps.
129+
Remove the polymorphic key from the initial data.
120130
"""
121-
try:
122-
map = cls.__dict__.get('_subtype_map', {})
131+
for subtype_key in cls.__dict__.get('_subtype_map', {}).keys():
132+
subtype_value = None
123133

124-
for _type, _classes in map.items():
125-
classification = response.get(_type)
126-
try:
127-
return objects[_classes[classification]]
128-
except KeyError:
129-
pass
134+
rest_api_response_key = _decode_attribute_map_key(cls._attribute_map[subtype_key]['key'])
135+
subtype_value = response.pop(rest_api_response_key, None) or response.pop(subtype_key, None)
136+
if subtype_value:
137+
flatten_mapping_type = cls._flatten_subtype(subtype_key, objects)
138+
return objects[flatten_mapping_type[subtype_value]]
139+
return cls
130140

131-
for c in _classes:
132-
try:
133-
_cls = objects[_classes[c]]
134-
return _cls._classify(response, objects)
135-
except (KeyError, TypeError):
136-
continue
137-
raise TypeError("Object cannot be classified futher.")
138-
except AttributeError:
139-
raise TypeError("Object cannot be classified futher.")
141+
def _decode_attribute_map_key(key):
142+
"""This decode a key in an _attribute_map to the actual key we want to look at
143+
inside the received data.
140144
145+
:param str key: A key string from the generated code
146+
"""
147+
return key.replace('\\.', '.')
141148

142149
def _convert_to_datatype(data, data_type, localtypes):
143150
if data is None:
@@ -157,6 +164,7 @@ def _convert_to_datatype(data, data_type, localtypes):
157164
elif issubclass(data_obj, Enum):
158165
return data
159166
elif not isinstance(data, data_obj):
167+
data_obj = data_obj._classify(data, localtypes)
160168
result = {
161169
key: _convert_to_datatype(
162170
data[key],
@@ -195,7 +203,7 @@ class Serializer(object):
195203
"unique": lambda x, y: len(x) != len(set(x)),
196204
"multiple": lambda x, y: x % y != 0
197205
}
198-
flattten = re.compile(r"(?<!\\)\.")
206+
flatten = re.compile(r"(?<!\\)\.")
199207

200208
def __init__(self, classes=None):
201209
self.serialize_type = {
@@ -241,14 +249,12 @@ def _serialize(self, target_obj, data_type=None, **kwargs):
241249

242250
try:
243251
attributes = target_obj._attribute_map
244-
self._classify_data(target_obj, class_name, serialized)
245-
246252
for attr, map in attributes.items():
247253
attr_name = attr
248254
debug_name = "{}.{}".format(class_name, attr_name)
249255
try:
250-
keys = self.flattten.split(map['key'])
251-
keys = [k.replace('\\.', '.') for k in keys]
256+
keys = self.flatten.split(map['key'])
257+
keys = [_decode_attribute_map_key(k) for k in keys]
252258
attr_type = map['type']
253259
orig_attr = getattr(target_obj, attr)
254260
validation = target_obj._validation.get(attr_name, {})
@@ -278,18 +284,6 @@ def _serialize(self, target_obj, data_type=None, **kwargs):
278284
else:
279285
return serialized
280286

281-
def _classify_data(self, target_obj, class_name, serialized):
282-
"""Check whether this object is a child and therefor needs to be
283-
classified in the message.
284-
"""
285-
try:
286-
for _type, _classes in target_obj._get_subtype_map().items():
287-
for ref, name in _classes.items():
288-
if name == class_name:
289-
serialized[_type] = ref
290-
except AttributeError:
291-
pass # TargetObj has no _subtype_map so we don't need to classify.
292-
293287
def body(self, data, data_type, **kwargs):
294288
"""Serialize data intended for a request body.
295289
@@ -752,9 +746,9 @@ def __call__(self, target_obj, response_data):
752746
while '.' in key:
753747
dict_keys = self.flatten.split(key)
754748
if len(dict_keys) == 1:
755-
key = dict_keys[0].replace('\\.', '.')
749+
key = _decode_attribute_map_key(dict_keys[0])
756750
break
757-
working_key = dict_keys[0].replace('\\.', '.')
751+
working_key = _decode_attribute_map_key(dict_keys[0])
758752
working_data = working_data.get(working_key, data)
759753
key = '.'.join(dict_keys[1:])
760754

@@ -786,8 +780,8 @@ def _classify_target(self, target, data):
786780

787781
try:
788782
target = target._classify(data, self.dependencies)
789-
except (TypeError, AttributeError):
790-
pass # Target has no subclasses, so can't classify further.
783+
except AttributeError:
784+
pass # Target is not a Model, no classify
791785
return target, target.__class__.__name__
792786

793787
def _unpack_content(self, raw_data):

0 commit comments

Comments
 (0)