Skip to content

Commit dd16873

Browse files
committed
Update docstrings for serialize, deserialize changes
2 parents 5582163 + 49746be commit dd16873

File tree

5 files changed

+206
-27
lines changed

5 files changed

+206
-27
lines changed

ask-sdk-core/ask_sdk_core/serialize.py

Lines changed: 91 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,19 @@ def serialize(self, obj):
6060
# type: (Any) -> Union[Dict[str, Any], List, Tuple, str, None]
6161
"""Builds a serialized object.
6262
63-
If obj is None, return None.
64-
If obj is str, int, long, float, bool, return directly.
65-
If obj is datetime.datetime, datetime.date convert to
66-
string in iso8601 format.
67-
If obj is list, serialize each element in the list.
68-
If obj is dict, return the dict with serialized values.
69-
If obj is ask sdk model, return the dict with keys resolved
70-
from model's ``attribute_map`` and values serialized
71-
based on ``deserialized_types``.
63+
* If obj is None, return None.
64+
* If obj is str, int, long, float, bool, return directly.
65+
* If obj is datetime.datetime, datetime.date convert to
66+
string in iso8601 format.
67+
* If obj is list, serialize each element in the list.
68+
* If obj is dict, return the dict with serialized values.
69+
* If obj is ask sdk model, return the dict with keys resolved
70+
from the union of model's ``attribute_map`` and
71+
``deserialized_types`` and values serialized based on
72+
``deserialized_types``.
73+
* If obj is a generic class instance, return the dict with keys
74+
from instance's ``deserialized_types`` and values serialized
75+
based on ``deserialized_types``.
7276
7377
:param obj: The data to serialize.
7478
:type obj: object
@@ -96,13 +100,22 @@ def serialize(self, obj):
96100
if isinstance(obj, dict):
97101
obj_dict = obj
98102
else:
99-
# Convert model obj to dict except
100-
# attributes `deserialized_types`, `attribute_map`
101-
# and attributes which value is not None.
102-
# Convert attribute name to json key in
103-
# model definition for request.
103+
# Convert model obj to dict
104+
# All the non null attributes under `deserialized_types`
105+
# map are considered for serialization.
106+
# The `attribute_map` provides the key names to be used
107+
# in the dict. In case of missing `attribute_map` mapping,
108+
# the original attribute name is retained as the key name.
109+
class_attribute_map = getattr(obj, 'attribute_map', {})
110+
class_attribute_map.update(
111+
{
112+
k: k for k in obj.deserialized_types.keys()
113+
if k not in class_attribute_map
114+
}
115+
)
116+
104117
obj_dict = {
105-
obj.attribute_map[attr]: getattr(obj, attr)
118+
class_attribute_map[attr]: getattr(obj, attr)
106119
for attr, _ in iteritems(obj.deserialized_types)
107120
if getattr(obj, attr) is not None
108121
}
@@ -111,7 +124,28 @@ def serialize(self, obj):
111124

112125
def deserialize(self, payload, obj_type):
113126
# type: (str, Union[T, str]) -> Any
114-
"""Deserializes payload into ask sdk model object.
127+
"""Deserializes payload into an instance of provided ``obj_type``.
128+
129+
The ``obj_type`` parameter can be a primitive type, a generic
130+
model object or a list / dict of model objects.
131+
132+
The list or dict object type has to be provided as a string
133+
format. For eg:
134+
135+
* ``'list[a.b.C]'`` if the payload is a list of instances of
136+
class ``a.b.C``.
137+
* ``'dict(str, a.b.C)'`` if the payload is a dict containing
138+
mappings of ``str : a.b.C`` class instance types.
139+
140+
The method looks for a ``deserialized_types`` dict in the model
141+
class, that mentions which payload values has to be
142+
deserialized. In case the payload key names are different than
143+
the model attribute names, the corresponding mapping can be
144+
provided in another special dict ``attribute_map``. The model
145+
class should also have the ``__init__`` method with default
146+
values for arguments. Check
147+
:py:class:`ask_sdk_model.request_envelope.RequestEnvelope`
148+
source code for an example implementation.
115149
116150
:param payload: data to be deserialized.
117151
:type payload: str
@@ -134,14 +168,14 @@ def deserialize(self, payload, obj_type):
134168

135169
def __deserialize(self, payload, obj_type):
136170
# type: (str, Union[T, str]) -> Any
137-
"""Deserializes payload into ask sdk model object.
171+
"""Deserializes payload into a model object.
138172
139173
:param payload: data to be deserialized.
140174
:type payload: str
141175
:param obj_type: resolved class name for deserialized object
142176
:type obj_type: Union[str, object]
143177
:return: deserialized object
144-
:rtype: T
178+
:rtype: object
145179
"""
146180
if payload is None:
147181
return None
@@ -179,7 +213,7 @@ def __deserialize(self, payload, obj_type):
179213
if obj_type in self.NATIVE_TYPES_MAPPING:
180214
obj_type = self.NATIVE_TYPES_MAPPING[obj_type]
181215
else:
182-
# deserialize ask sdk models
216+
# deserialize models
183217
obj_type = self.__load_class_from_name(obj_type)
184218

185219
if obj_type in self.PRIMITIVE_TYPES:
@@ -194,7 +228,20 @@ def __deserialize(self, payload, obj_type):
194228
return self.__deserialize_model(payload, obj_type)
195229

196230
def __load_class_from_name(self, class_name):
197-
# type: (str) -> str
231+
# type: (str) -> T
232+
"""Load the class from the ``class_name`` provided.
233+
234+
Resolve the class name from the ``class_name`` provided, load
235+
the class on path and return the resolved class. If the module
236+
information is not provided in the ``class_name``, then look
237+
for the class on sys ``modules``.
238+
239+
:param class_name: absolute class name to be loaded
240+
:type class_name: str
241+
:return: Resolved class reference
242+
:rtype: object
243+
:raises: :py:class:`ask_sdk_core.exceptions.SerializationException`
244+
"""
198245
try:
199246
module_class_list = class_name.rsplit(".", 1)
200247
if len(module_class_list) > 1:
@@ -223,7 +270,7 @@ def __deserialize_primitive(self, payload, obj_type):
223270
:type obj_type: object
224271
:return: deserialized primitive datatype object
225272
:rtype: object
226-
:raises SerializationException
273+
:raises: :py:class:`ask_sdk_core.exceptions.SerializationException`
227274
"""
228275
try:
229276
return obj_type(payload)
@@ -247,7 +294,7 @@ def __deserialize_datetime(self, payload, obj_type):
247294
:type obj_type: object
248295
:return: deserialized primitive datatype object
249296
:rtype: object
250-
:raises SerializationException
297+
:raises: :py:class:`ask_sdk_core.exceptions.SerializationException`
251298
"""
252299
try:
253300
from dateutil.parser import parse
@@ -273,20 +320,25 @@ def __deserialize_model(self, payload, obj_type):
273320
:type obj_type: object
274321
:return: deserialized sdk model object
275322
:rtype: object
276-
:raises SerializationException
323+
:raises: :py:class:`ask_sdk_core.exceptions.SerializationException`
277324
"""
278325
try:
279326
if issubclass(obj_type, Enum):
280327
return obj_type(payload)
281328

282-
if hasattr(obj_type, 'deserialized_types') and hasattr(
283-
obj_type, 'attribute_map'):
329+
if hasattr(obj_type, 'deserialized_types'):
284330
if hasattr(obj_type, 'get_real_child_model'):
285331
obj_type = self.__get_obj_by_discriminator(
286332
payload, obj_type)
287333

288334
class_deserialized_types = obj_type.deserialized_types
289-
class_attribute_map = obj_type.attribute_map
335+
class_attribute_map = getattr(obj_type, 'attribute_map', {})
336+
class_attribute_map.update(
337+
{
338+
k: k for k in obj_type.deserialized_types.keys()
339+
if k not in class_attribute_map
340+
}
341+
)
290342

291343
deserialized_model = obj_type()
292344
for class_param_name, payload_param_name in iteritems(
@@ -312,7 +364,19 @@ def __deserialize_model(self, payload, obj_type):
312364
raise SerializationException(str(e))
313365

314366
def __get_obj_by_discriminator(self, payload, obj_type):
315-
# type: (str, Union[T, str]) -> str
367+
# type: (str, Union[T, str]) -> T
368+
"""Get correct subclass instance using the discriminator in
369+
payload.
370+
371+
:param payload: Payload for deserialization
372+
:type payload: str
373+
:param obj_type: parent class for deserializing payload into
374+
:type obj_type: object
375+
:return: Subclass of provided parent class, that resolves to
376+
the discriminator in payload.
377+
:rtype: object
378+
:raises: :py:class:`ask_sdk_core.exceptions.SerializationException`
379+
"""
316380
namespaced_class_name = obj_type.get_real_child_model(payload)
317381
if not namespaced_class_name:
318382
raise SerializationException(

ask-sdk-core/tests/unit/data/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
# specific language governing permissions and limitations under the
1616
# License.
1717
#
18+
1819
from .model_enum_object import ModelEnumObject
1920
from .model_test_object_1 import ModelTestObject1
2021
from .model_test_object_2 import ModelTestObject2
22+
from .model_test_object_3 import ModelTestObject3
23+
from .model_test_object_4 import ModelTestObject4
2124
from .invalid_model_object import InvalidModelObject
2225
from .model_abstract_parent_object import ModelAbstractParentObject
2326
from .model_child_objects import ModelChildObject1
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights
4+
# Reserved.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License").
7+
# You may not use this file except in compliance with the License.
8+
# A copy of the License is located at
9+
#
10+
# http://aws.amazon.com/apache2.0/
11+
#
12+
# or in the "license" file accompanying this file. This file is
13+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
14+
# OF ANY KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations under the
16+
# License.
17+
#
18+
19+
20+
class ModelTestObject3(object):
21+
deserialized_types = {
22+
'str_var': 'str',
23+
'int_var': 'int'
24+
}
25+
26+
def __init__(self, str_var=None, int_var=None):
27+
self.str_var = str_var
28+
self.int_var = int_var
29+
30+
def __eq__(self, other):
31+
return self.__dict__ == other.__dict__
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights
4+
# Reserved.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License").
7+
# You may not use this file except in compliance with the License.
8+
# A copy of the License is located at
9+
#
10+
# http://aws.amazon.com/apache2.0/
11+
#
12+
# or in the "license" file accompanying this file. This file is
13+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
14+
# OF ANY KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations under the
16+
# License.
17+
#
18+
19+
20+
class ModelTestObject4(object):
21+
deserialized_types = {
22+
'str_var': 'str',
23+
'float_var': 'float'
24+
}
25+
26+
attribute_map = {
27+
'float_var': 'floatingValue'
28+
}
29+
30+
def __init__(self, str_var=None, float_var=None):
31+
self.str_var = str_var
32+
self.float_var = float_var
33+
34+
def __eq__(self, other):
35+
return self.__dict__ == other.__dict__

ask-sdk-core/tests/unit/test_serialize.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,24 @@ def test_model_obj_serialization(self):
132132
assert self.test_serializer.serialize(test_model_obj_1) == expected_serialized_obj, \
133133
"Default Serializer serialized model object incorrectly"
134134

135+
def test_model_obj_without_attrmap_serialization(self):
136+
test_obj_inst = data.ModelTestObject3(str_var="test", int_var=123)
137+
expected_dict = {
138+
"str_var": "test",
139+
"int_var": 123
140+
}
141+
assert self.test_serializer.serialize(test_obj_inst) == expected_dict, \
142+
"Default Serializer serialized object without attribute_map incorrectly"
143+
144+
def test_model_obj_with_incomplete_attrmap_serialization(self):
145+
test_obj_inst = data.ModelTestObject4(str_var="test", float_var=3.14)
146+
expected_dict = {
147+
"str_var": "test",
148+
"floatingValue": 3.14
149+
}
150+
assert self.test_serializer.serialize(test_obj_inst) == expected_dict, \
151+
"Default Serializer serialized object with incomplete attribute map incorrectly"
152+
135153
def test_enum_obj_serialization(self):
136154
test_model_obj_2 = data.ModelTestObject2(int_var=123)
137155
test_enum_obj = data.ModelEnumObject("ENUM_VAL_1")
@@ -406,6 +424,34 @@ def test_model_obj_with_additional_params_in_payload_deserialization(self):
406424
test_payload, test_obj_type) == expected_obj, (
407425
"Default Serializer deserialized model object incorrectly when payload has additional parameters")
408426

427+
def test_model_obj_without_attrmap_deserialization(self):
428+
test_payload = {
429+
"str_var": "Test",
430+
"int_var": 123
431+
}
432+
test_obj_type = data.ModelTestObject3
433+
expected_obj = data.ModelTestObject3(str_var="Test", int_var=123)
434+
435+
with patch("json.loads") as mock_json_loader:
436+
mock_json_loader.return_value = test_payload
437+
assert self.test_serializer.deserialize(
438+
test_payload, test_obj_type) == expected_obj, (
439+
"Default Serializer deserialized model object without attribute map incorrectly")
440+
441+
def test_model_obj_with_incomplete_attrmap_deserialization(self):
442+
test_payload = {
443+
"str_var": "Test",
444+
"floatingValue": 3.14
445+
}
446+
test_obj_type = data.ModelTestObject4
447+
expected_obj = data.ModelTestObject4(str_var="Test", float_var=3.14)
448+
449+
with patch("json.loads") as mock_json_loader:
450+
mock_json_loader.return_value = test_payload
451+
assert self.test_serializer.deserialize(
452+
test_payload, test_obj_type) == expected_obj, (
453+
"Default Serializer deserialized model object with incomplete attribute map incorrectly")
454+
409455
def test_invalid_model_obj_deserialization(self):
410456
test_payload = {
411457
"var_1": "some value"

0 commit comments

Comments
 (0)