1
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License"). You
4
# may not use this file except in compliance with the License. A copy of
5
# the License is located at
6
#
7
# http://aws.amazon.com/apache2.0/
8
#
9
# or in the "license" file accompanying this file. This file is
10
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
# ANY KIND, either express or implied. See the License for the specific
12
# language governing permissions and limitations under the License.
13 11
"""Protocol input serializes.
14

15
This module contains classes that implement input serialization
16
for the various AWS protocol types.
17

18
These classes essentially take user input, a model object that
19
represents what the expected input should look like, and it returns
20
a dictionary that contains the various parts of a request.  A few
21
high level design decisions:
22

23

24
* Each protocol type maps to a separate class, all inherit from
25
  ``Serializer``.
26
* The return value for ``serialize_to_request`` (the main entry
27
  point) returns a dictionary that represents a request.  This
28
  will have keys like ``url_path``, ``query_string``, etc.  This
29
  is done so that it's a) easy to test and b) not tied to a
30
  particular HTTP library.  See the ``serialize_to_request`` docstring
31
  for more details.
32

33
Unicode
34
-------
35

36
The input to the serializers should be text (str/unicode), not bytes,
37
with the exception of blob types.  Those are assumed to be binary,
38
and if a str/unicode type is passed in, it will be encoded as utf-8.
39
"""
40 11
import re
41 11
import base64
42 11
import calendar
43 11
import datetime
44 11
from xml.etree import ElementTree
45

46 11
from botocore.compat import six
47

48 11
from botocore.compat import json, formatdate
49 11
from botocore.utils import parse_to_aware_datetime
50 11
from botocore.utils import percent_encode
51 11
from botocore.utils import is_json_value_header
52 11
from botocore.utils import conditionally_calculate_md5
53 11
from botocore import validate
54

55

56
# From the spec, the default timestamp format if not specified is iso8601.
57 11
DEFAULT_TIMESTAMP_FORMAT = 'iso8601'
58 11
ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
59
# Same as ISO8601, but with microsecond precision.
60 11
ISO8601_MICRO = '%Y-%m-%dT%H:%M:%S.%fZ'
61

62

63 11
def create_serializer(protocol_name, include_validation=True):
64
    # TODO: Unknown protocols.
65 11
    serializer = SERIALIZERS[protocol_name]()
66 11
    if include_validation:
67 11
        validator = validate.ParamValidator()
68 11
        serializer = validate.ParamValidationDecorator(validator, serializer)
69 11
    return serializer
70

71

72 11
class Serializer(object):
73 11
    DEFAULT_METHOD = 'POST'
74
    # Clients can change this to a different MutableMapping
75
    # (i.e OrderedDict) if they want.  This is used in the
76
    # compliance test to match the hash ordering used in the
77
    # tests.
78 11
    MAP_TYPE = dict
79 11
    DEFAULT_ENCODING = 'utf-8'
80

81 11
    def serialize_to_request(self, parameters, operation_model):
82
        """Serialize parameters into an HTTP request.
83

84
        This method takes user provided parameters and a shape
85
        model and serializes the parameters to an HTTP request.
86
        More specifically, this method returns information about
87
        parts of the HTTP request, it does not enforce a particular
88
        interface or standard for an HTTP request.  It instead returns
89
        a dictionary of:
90

91
            * 'url_path'
92
            * 'host_prefix'
93
            * 'query_string'
94
            * 'headers'
95
            * 'body'
96
            * 'method'
97

98
        It is then up to consumers to decide how to map this to a Request
99
        object of their HTTP library of choice.  Below is an example
100
        return value::
101

102
            {'body': {'Action': 'OperationName',
103
                      'Bar': 'val2',
104
                      'Foo': 'val1',
105
                      'Version': '2014-01-01'},
106
             'headers': {},
107
             'method': 'POST',
108
             'query_string': '',
109
             'host_prefix': 'value.',
110
             'url_path': '/'}
111

112
        :param parameters: The dictionary input parameters for the
113
            operation (i.e the user input).
114
        :param operation_model: The OperationModel object that describes
115
            the operation.
116
        """
117
        raise NotImplementedError("serialize_to_request")
118

119 11
    def _create_default_request(self):
120
        # Creates a boilerplate default request dict that subclasses
121
        # can use as a starting point.
122 11
        serialized = {
123
            'url_path': '/',
124
            'query_string': '',
125
            'method': self.DEFAULT_METHOD,
126
            'headers': {},
127
            # An empty body is represented as an empty byte string.
128
            'body': b''
129
        }
130 11
        return serialized
131

132
    # Some extra utility methods subclasses can use.
133

134 11
    def _timestamp_iso8601(self, value):
135 11
        if value.microsecond > 0:
136 11
            timestamp_format = ISO8601_MICRO
137
        else:
138 11
            timestamp_format = ISO8601
139 11
        return value.strftime(timestamp_format)
140

141 11
    def _timestamp_unixtimestamp(self, value):
142 11
        return int(calendar.timegm(value.timetuple()))
143

144 11
    def _timestamp_rfc822(self, value):
145 11
        if isinstance(value, datetime.datetime):
146 11
            value = self._timestamp_unixtimestamp(value)
147 11
        return formatdate(value, usegmt=True)
148

149 11
    def _convert_timestamp_to_str(self, value, timestamp_format=None):
150 11
        if timestamp_format is None:
151 11
            timestamp_format = self.TIMESTAMP_FORMAT
152 11
        timestamp_format = timestamp_format.lower()
153 11
        datetime_obj = parse_to_aware_datetime(value)
154 11
        converter = getattr(
155
            self, '_timestamp_%s' % timestamp_format)
156 11
        final_value = converter(datetime_obj)
157 11
        return final_value
158

159 11
    def _get_serialized_name(self, shape, default_name):
160
        # Returns the serialized name for the shape if it exists.
161
        # Otherwise it will return the passed in default_name.
162 11
        return shape.serialization.get('name', default_name)
163

164 11
    def _get_base64(self, value):
165
        # Returns the base64-encoded version of value, handling
166
        # both strings and bytes. The returned value is a string
167
        # via the default encoding.
168 11
        if isinstance(value, six.text_type):
169 11
            value = value.encode(self.DEFAULT_ENCODING)
170 11
        return base64.b64encode(value).strip().decode(
171
            self.DEFAULT_ENCODING)
172

173 11
    def _expand_host_prefix(self, parameters, operation_model):
174 11
        operation_endpoint = operation_model.endpoint
175 11
        if operation_endpoint is None:
176 11
            return None
177

178 11
        host_prefix_expression = operation_endpoint['hostPrefix']
179 11
        input_members = operation_model.input_shape.members
180 11
        host_labels = [
181
            member for member, shape in input_members.items()
182
            if shape.serialization.get('hostLabel')
183
        ]
184 11
        format_kwargs = dict((name, parameters[name]) for name in host_labels)
185

186 11
        return host_prefix_expression.format(**format_kwargs)
187

188 11
    def _prepare_additional_traits(self, request, operation_model):
189
        """Determine if additional traits are required for given model"""
190 11
        if operation_model.http_checksum_required:
191 11
            conditionally_calculate_md5(request)
192 11
        return request
193

194

195 11
class QuerySerializer(Serializer):
196

197 11
    TIMESTAMP_FORMAT = 'iso8601'
198

199 11
    def serialize_to_request(self, parameters, operation_model):
200 11
        shape = operation_model.input_shape
201 11
        serialized = self._create_default_request()
202 11
        serialized['method'] = operation_model.http.get('method',
203
                                                        self.DEFAULT_METHOD)
204 11
        serialized['headers'] = {
205
            'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
206
        }
207
        # The query serializer only deals with body params so
208
        # that's what we hand off the _serialize_* methods.
209 11
        body_params = self.MAP_TYPE()
210 11
        body_params['Action'] = operation_model.name
211 11
        body_params['Version'] = operation_model.metadata['apiVersion']
212 11
        if shape is not None:
213 11
            self._serialize(body_params, parameters, shape)
214 11
        serialized['body'] = body_params
215

216 11
        host_prefix = self._expand_host_prefix(parameters, operation_model)
217 11
        if host_prefix is not None:
218 11
            serialized['host_prefix'] = host_prefix
219

220 11
        serialized = self._prepare_additional_traits(serialized,
221
                operation_model)
222 11
        return serialized
223

224 11
    def _serialize(self, serialized, value, shape, prefix=''):
225
        # serialized: The dict that is incrementally added to with the
226
        #             final serialized parameters.
227
        # value: The current user input value.
228
        # shape: The shape object that describes the structure of the
229
        #        input.
230
        # prefix: The incrementally built up prefix for the serialized
231
        #         key (i.e Foo.bar.members.1).
232 11
        method = getattr(self, '_serialize_type_%s' % shape.type_name,
233
                         self._default_serialize)
234 11
        method(serialized, value, shape, prefix=prefix)
235

236 11
    def _serialize_type_structure(self, serialized, value, shape, prefix=''):
237 11
        members = shape.members
238 11
        for key, value in value.items():
239 11
            member_shape = members[key]
240 11
            member_prefix = self._get_serialized_name(member_shape, key)
241 11
            if prefix:
242 11
                member_prefix = '%s.%s' % (prefix, member_prefix)
243 11
            self._serialize(serialized, value, member_shape, member_prefix)
244

245 11
    def _serialize_type_list(self, serialized, value, shape, prefix=''):
246 11
        if not value:
247
            # The query protocol serializes empty lists.
248 11
            serialized[prefix] = ''
249 11
            return
250 11
        if self._is_shape_flattened(shape):
251 11
            list_prefix = prefix
252 11
            if shape.member.serialization.get('name'):
253 11
                name = self._get_serialized_name(shape.member, default_name='')
254
                # Replace '.Original' with '.{name}'.
255 11
                list_prefix = '.'.join(prefix.split('.')[:-1] + [name])
256
        else:
257 11
            list_name = shape.member.serialization.get('name', 'member')
258 11
            list_prefix = '%s.%s' % (prefix, list_name)
259 11
        for i, element in enumerate(value, 1):
260 11
            element_prefix = '%s.%s' % (list_prefix, i)
261 11
            element_shape = shape.member
262 11
            self._serialize(serialized, element, element_shape, element_prefix)
263

264 11
    def _serialize_type_map(self, serialized, value, shape, prefix=''):
265 11
        if self._is_shape_flattened(shape):
266 11
            full_prefix = prefix
267
        else:
268 11
            full_prefix = '%s.entry' % prefix
269 11
        template = full_prefix + '.{i}.{suffix}'
270 11
        key_shape = shape.key
271 11
        value_shape = shape.value
272 11
        key_suffix = self._get_serialized_name(key_shape, default_name='key')
273 11
        value_suffix = self._get_serialized_name(value_shape, 'value')
274 11
        for i, key in enumerate(value, 1):
275 11
            key_prefix = template.format(i=i, suffix=key_suffix)
276 11
            value_prefix = template.format(i=i, suffix=value_suffix)
277 11
            self._serialize(serialized, key, key_shape, key_prefix)
278 11
            self._serialize(serialized, value[key], value_shape, value_prefix)
279

280 11
    def _serialize_type_blob(self, serialized, value, shape, prefix=''):
281
        # Blob args must be base64 encoded.
282 11
        serialized[prefix] = self._get_base64(value)
283

284 11
    def _serialize_type_timestamp(self, serialized, value, shape, prefix=''):
285 11
        serialized[prefix] = self._convert_timestamp_to_str(
286
            value, shape.serialization.get('timestampFormat'))
287

288 11
    def _serialize_type_boolean(self, serialized, value, shape, prefix=''):
289 11
        if value:
290 11
            serialized[prefix] = 'true'
291
        else:
292 11
            serialized[prefix] = 'false'
293

294 11
    def _default_serialize(self, serialized, value, shape, prefix=''):
295 11
        serialized[prefix] = value
296

297 11
    def _is_shape_flattened(self, shape):
298 11
        return shape.serialization.get('flattened')
299

300

301 11
class EC2Serializer(QuerySerializer):
302
    """EC2 specific customizations to the query protocol serializers.
303

304
    The EC2 model is almost, but not exactly, similar to the query protocol
305
    serializer.  This class encapsulates those differences.  The model
306
    will have be marked with a ``protocol`` of ``ec2``, so you don't need
307
    to worry about wiring this class up correctly.
308

309
    """
310

311 11
    def _get_serialized_name(self, shape, default_name):
312
        # Returns the serialized name for the shape if it exists.
313
        # Otherwise it will return the passed in default_name.
314 11
        if 'queryName' in shape.serialization:
315 11
            return shape.serialization['queryName']
316 11
        elif 'name' in shape.serialization:
317
            # A locationName is always capitalized
318
            # on input for the ec2 protocol.
319 11
            name = shape.serialization['name']
320 11
            return name[0].upper() + name[1:]
321
        else:
322 11
            return default_name
323

324 11
    def _serialize_type_list(self, serialized, value, shape, prefix=''):
325 11
        for i, element in enumerate(value, 1):
326 11
            element_prefix = '%s.%s' % (prefix, i)
327 11
            element_shape = shape.member
328 11
            self._serialize(serialized, element, element_shape, element_prefix)
329

330

331 11
class JSONSerializer(Serializer):
332 11
    TIMESTAMP_FORMAT = 'unixtimestamp'
333

334 11
    def serialize_to_request(self, parameters, operation_model):
335 11
        target = '%s.%s' % (operation_model.metadata['targetPrefix'],
336
                            operation_model.name)
337 11
        json_version = operation_model.metadata['jsonVersion']
338 11
        serialized = self._create_default_request()
339 11
        serialized['method'] = operation_model.http.get('method',
340
                                                        self.DEFAULT_METHOD)
341 11
        serialized['headers'] = {
342
            'X-Amz-Target': target,
343
            'Content-Type': 'application/x-amz-json-%s' % json_version,
344
        }
345 11
        body = self.MAP_TYPE()
346 11
        input_shape = operation_model.input_shape
347 11
        if input_shape is not None:
348 11
            self._serialize(body, parameters, input_shape)
349 11
        serialized['body'] = json.dumps(body).encode(self.DEFAULT_ENCODING)
350

351 11
        host_prefix = self._expand_host_prefix(parameters, operation_model)
352 11
        if host_prefix is not None:
353 11
            serialized['host_prefix'] = host_prefix
354

355 11
        serialized = self._prepare_additional_traits(serialized,
356
                operation_model)
357 11
        return serialized
358

359 11
    def _serialize(self, serialized, value, shape, key=None):
360 11
        method = getattr(self, '_serialize_type_%s' % shape.type_name,
361
                         self._default_serialize)
362 11
        method(serialized, value, shape, key)
363

364 11
    def _serialize_type_structure(self, serialized, value, shape, key):
365 11
        if key is not None:
366
            # If a key is provided, this is a result of a recursive
367
            # call so we need to add a new child dict as the value
368
            # of the passed in serialized dict.  We'll then add
369
            # all the structure members as key/vals in the new serialized
370
            # dictionary we just created.
371 11
            new_serialized = self.MAP_TYPE()
372 11
            serialized[key] = new_serialized
373 11
            serialized = new_serialized
374 11
        members = shape.members
375 11
        for member_key, member_value in value.items():
376 11
            member_shape = members[member_key]
377 11
            if 'name' in member_shape.serialization:
378 11
                member_key = member_shape.serialization['name']
379 11
            self._serialize(serialized, member_value, member_shape, member_key)
380

381 11
    def _serialize_type_map(self, serialized, value, shape, key):
382 11
        map_obj = self.MAP_TYPE()
383 11
        serialized[key] = map_obj
384 11
        for sub_key, sub_value in value.items():
385 11
            self._serialize(map_obj, sub_value, shape.value, sub_key)
386

387 11
    def _serialize_type_list(self, serialized, value, shape, key):
388 11
        list_obj = []
389 11
        serialized[key] = list_obj
390 11
        for list_item in value:
391 11
            wrapper = {}
392
            # The JSON list serialization is the only case where we aren't
393
            # setting a key on a dict.  We handle this by using
394
            # a __current__ key on a wrapper dict to serialize each
395
            # list item before appending it to the serialized list.
396 11
            self._serialize(wrapper, list_item, shape.member, "__current__")
397 11
            list_obj.append(wrapper["__current__"])
398

399 11
    def _default_serialize(self, serialized, value, shape, key):
400 11
        serialized[key] = value
401

402 11
    def _serialize_type_timestamp(self, serialized, value, shape, key):
403 11
        serialized[key] = self._convert_timestamp_to_str(
404
            value, shape.serialization.get('timestampFormat'))
405

406 11
    def _serialize_type_blob(self, serialized, value, shape, key):
407 11
        serialized[key] = self._get_base64(value)
408

409

410 11
class BaseRestSerializer(Serializer):
411
    """Base class for rest protocols.
412

413
    The only variance between the various rest protocols is the
414
    way that the body is serialized.  All other aspects (headers, uri, etc.)
415
    are the same and logic for serializing those aspects lives here.
416

417
    Subclasses must implement the ``_serialize_body_params`` method.
418

419
    """
420 11
    QUERY_STRING_TIMESTAMP_FORMAT = 'iso8601'
421 11
    HEADER_TIMESTAMP_FORMAT = 'rfc822'
422
    # This is a list of known values for the "location" key in the
423
    # serialization dict.  The location key tells us where on the request
424
    # to put the serialized value.
425 11
    KNOWN_LOCATIONS = ['uri', 'querystring', 'header', 'headers']
426

427 11
    def serialize_to_request(self, parameters, operation_model):
428 11
        serialized = self._create_default_request()
429 11
        serialized['method'] = operation_model.http.get('method',
430
                                                        self.DEFAULT_METHOD)
431 11
        shape = operation_model.input_shape
432 11
        if shape is None:
433 11
            serialized['url_path'] = operation_model.http['requestUri']
434 11
            return serialized
435 11
        shape_members = shape.members
436
        # While the ``serialized`` key holds the final serialized request
437
        # data, we need interim dicts for the various locations of the
438
        # request.  We need this for the uri_path_kwargs and the
439
        # query_string_kwargs because they are templated, so we need
440
        # to gather all the needed data for the string template,
441
        # then we render the template.  The body_kwargs is needed
442
        # because once we've collected them all, we run them through
443
        # _serialize_body_params, which for rest-json, creates JSON,
444
        # and for rest-xml, will create XML.  This is what the
445
        # ``partitioned`` dict below is for.
446 11
        partitioned = {
447
            'uri_path_kwargs': self.MAP_TYPE(),
448
            'query_string_kwargs': self.MAP_TYPE(),
449
            'body_kwargs': self.MAP_TYPE(),
450
            'headers': self.MAP_TYPE(),
451
        }
452 11
        for param_name, param_value in parameters.items():
453 11
            if param_value is None:
454
                # Don't serialize any parameter with a None value.
455 11
                continue
456 11
            self._partition_parameters(partitioned, param_name, param_value,
457
                                       shape_members)
458 11
        serialized['url_path'] = self._render_uri_template(
459
            operation_model.http['requestUri'],
460
            partitioned['uri_path_kwargs'])
461
        # Note that we lean on the http implementation to handle the case
462
        # where the requestUri path already has query parameters.
463
        # The bundled http client, requests, already supports this.
464 11
        serialized['query_string'] = partitioned['query_string_kwargs']
465 11
        if partitioned['headers']:
466 11
            serialized['headers'] = partitioned['headers']
467 11
        self._serialize_payload(partitioned, parameters,
468
                                serialized, shape, shape_members)
469

470 11
        host_prefix = self._expand_host_prefix(parameters, operation_model)
471 11
        if host_prefix is not None:
472 11
            serialized['host_prefix'] = host_prefix
473

474 11
        serialized = self._prepare_additional_traits(serialized,
475
                operation_model)
476 11
        return serialized
477

478 11
    def _render_uri_template(self, uri_template, params):
479
        # We need to handle two cases::
480
        #
481
        # /{Bucket}/foo
482
        # /{Key+}/bar
483
        # A label ending with '+' is greedy.  There can only
484
        # be one greedy key.
485 11
        encoded_params = {}
486 11
        for template_param in re.findall(r'{(.*?)}', uri_template):
487 11
            if template_param.endswith('+'):
488 11
                encoded_params[template_param] = percent_encode(
489
                    params[template_param[:-1]], safe='/~')
490
            else:
491 11
                encoded_params[template_param] = percent_encode(
492
                    params[template_param])
493 11
        return uri_template.format(**encoded_params)
494

495 11
    def _serialize_payload(self, partitioned, parameters,
496
                           serialized, shape, shape_members):
497
        # partitioned - The user input params partitioned by location.
498
        # parameters - The user input params.
499
        # serialized - The final serialized request dict.
500
        # shape - Describes the expected input shape
501
        # shape_members - The members of the input struct shape
502 11
        payload_member = shape.serialization.get('payload')
503 11
        if payload_member is not None and \
504
                shape_members[payload_member].type_name in ['blob', 'string']:
505
            # If it's streaming, then the body is just the
506
            # value of the payload.
507 11
            body_payload = parameters.get(payload_member, b'')
508 11
            body_payload = self._encode_payload(body_payload)
509 11
            serialized['body'] = body_payload
510 11
        elif payload_member is not None:
511
            # If there's a payload member, we serialized that
512
            # member to they body.
513 11
            body_params = parameters.get(payload_member)
514 11
            if body_params is not None:
515 11
                serialized['body'] = self._serialize_body_params(
516
                    body_params,
517
                    shape_members[payload_member])
518 11
        elif partitioned['body_kwargs']:
519 11
            serialized['body'] = self._serialize_body_params(
520
                partitioned['body_kwargs'], shape)
521

522 11
    def _encode_payload(self, body):
523 11
        if isinstance(body, six.text_type):
524 11
            return body.encode(self.DEFAULT_ENCODING)
525 11
        return body
526

527 11
    def _partition_parameters(self, partitioned, param_name,
528
                              param_value, shape_members):
529
        # This takes the user provided input parameter (``param``)
530
        # and figures out where they go in the request dict.
531
        # Some params are HTTP headers, some are used in the URI, some
532
        # are in the request body.  This method deals with this.
533 11
        member = shape_members[param_name]
534 11
        location = member.serialization.get('location')
535 11
        key_name = member.serialization.get('name', param_name)
536 11
        if location == 'uri':
537 11
            partitioned['uri_path_kwargs'][key_name] = param_value
538 11
        elif location == 'querystring':
539 11
            if isinstance(param_value, dict):
540 11
                partitioned['query_string_kwargs'].update(param_value)
541 11
            elif isinstance(param_value, bool):
542 11
                partitioned['query_string_kwargs'][
543
                    key_name] = str(param_value).lower()
544 11
            elif member.type_name == 'timestamp':
545 11
                timestamp_format = member.serialization.get(
546
                    'timestampFormat', self.QUERY_STRING_TIMESTAMP_FORMAT)
547 11
                partitioned['query_string_kwargs'][
548
                    key_name] = self._convert_timestamp_to_str(
549
                        param_value, timestamp_format
550
                    )
551
            else:
552 11
                partitioned['query_string_kwargs'][key_name] = param_value
553 11
        elif location == 'header':
554 11
            shape = shape_members[param_name]
555 11
            value = self._convert_header_value(shape, param_value)
556 11
            partitioned['headers'][key_name] = str(value)
557 11
        elif location == 'headers':
558
            # 'headers' is a bit of an oddball.  The ``key_name``
559
            # is actually really a prefix for the header names:
560 11
            header_prefix = key_name
561
            # The value provided by the user is a dict so we'll be
562
            # creating multiple header key/val pairs.  The key
563
            # name to use for each header is the header_prefix (``key_name``)
564
            # plus the key provided by the user.
565 11
            self._do_serialize_header_map(header_prefix,
566
                                          partitioned['headers'],
567
                                          param_value)
568
        else:
569 11
            partitioned['body_kwargs'][param_name] = param_value
570

571 11
    def _do_serialize_header_map(self, header_prefix, headers, user_input):
572 11
        for key, val in user_input.items():
573 11
            full_key = header_prefix + key
574 11
            headers[full_key] = val
575

576 11
    def _serialize_body_params(self, params, shape):
577
        raise NotImplementedError('_serialize_body_params')
578

579 11
    def _convert_header_value(self, shape, value):
580 11
        if shape.type_name == 'timestamp':
581 11
            datetime_obj = parse_to_aware_datetime(value)
582 11
            timestamp = calendar.timegm(datetime_obj.utctimetuple())
583 11
            timestamp_format = shape.serialization.get(
584
                'timestampFormat', self.HEADER_TIMESTAMP_FORMAT)
585 11
            return self._convert_timestamp_to_str(timestamp, timestamp_format)
586 11
        elif is_json_value_header(shape):
587
            # Serialize with no spaces after separators to save space in
588
            # the header.
589 11
            return self._get_base64(json.dumps(value, separators=(',', ':')))
590
        else:
591 11
            return value
592

593

594 11
class RestJSONSerializer(BaseRestSerializer, JSONSerializer):
595

596 11
    def _serialize_body_params(self, params, shape):
597 11
        serialized_body = self.MAP_TYPE()
598 11
        self._serialize(serialized_body, params, shape)
599 11
        return json.dumps(serialized_body).encode(self.DEFAULT_ENCODING)
600

601

602 11
class RestXMLSerializer(BaseRestSerializer):
603 11
    TIMESTAMP_FORMAT = 'iso8601'
604

605 11
    def _serialize_body_params(self, params, shape):
606 11
        root_name = shape.serialization['name']
607 11
        pseudo_root = ElementTree.Element('')
608 11
        self._serialize(shape, params, pseudo_root, root_name)
609 11
        real_root = list(pseudo_root)[0]
610 11
        return ElementTree.tostring(real_root, encoding=self.DEFAULT_ENCODING)
611

612 11
    def _serialize(self, shape, params, xmlnode, name):
613 11
        method = getattr(self, '_serialize_type_%s' % shape.type_name,
614
                         self._default_serialize)
615 11
        method(xmlnode, params, shape, name)
616

617 11
    def _serialize_type_structure(self, xmlnode, params, shape, name):
618 11
        structure_node = ElementTree.SubElement(xmlnode, name)
619

620 11
        if 'xmlNamespace' in shape.serialization:
621 11
            namespace_metadata = shape.serialization['xmlNamespace']
622 11
            attribute_name = 'xmlns'
623 11
            if namespace_metadata.get('prefix'):
624 11
                attribute_name += ':%s' % namespace_metadata['prefix']
625 11
            structure_node.attrib[attribute_name] = namespace_metadata['uri']
626 11
        for key, value in params.items():
627 11
            member_shape = shape.members[key]
628 11
            member_name = member_shape.serialization.get('name', key)
629
            # We need to special case member shapes that are marked as an
630
            # xmlAttribute.  Rather than serializing into an XML child node,
631
            # we instead serialize the shape to an XML attribute of the
632
            # *current* node.
633 11
            if value is None:
634
                # Don't serialize any param whose value is None.
635 11
                return
636 11
            if member_shape.serialization.get('xmlAttribute'):
637
                # xmlAttributes must have a serialization name.
638 11
                xml_attribute_name = member_shape.serialization['name']
639 11
                structure_node.attrib[xml_attribute_name] = value
640 11
                continue
641 11
            self._serialize(member_shape, value, structure_node, member_name)
642

643 11
    def _serialize_type_list(self, xmlnode, params, shape, name):
644 11
        member_shape = shape.member
645 11
        if shape.serialization.get('flattened'):
646 11
            element_name = name
647 11
            list_node = xmlnode
648
        else:
649 11
            element_name = member_shape.serialization.get('name', 'member')
650 11
            list_node = ElementTree.SubElement(xmlnode, name)
651 11
        for item in params:
652 11
            self._serialize(member_shape, item, list_node, element_name)
653

654 11
    def _serialize_type_map(self, xmlnode, params, shape, name):
655
        # Given the ``name`` of MyMap, and input of {"key1": "val1"}
656
        # we serialize this as:
657
        #   <MyMap>
658
        #     <entry>
659
        #       <key>key1</key>
660
        #       <value>val1</value>
661
        #     </entry>
662
        #  </MyMap>
663 11
        node = ElementTree.SubElement(xmlnode, name)
664
        # TODO: handle flattened maps.
665 11
        for key, value in params.items():
666 11
            entry_node = ElementTree.SubElement(node, 'entry')
667 11
            key_name = self._get_serialized_name(shape.key, default_name='key')
668 11
            val_name = self._get_serialized_name(shape.value,
669
                                                 default_name='value')
670 11
            self._serialize(shape.key, key, entry_node, key_name)
671 11
            self._serialize(shape.value, value, entry_node, val_name)
672

673 11
    def _serialize_type_boolean(self, xmlnode, params, shape, name):
674
        # For scalar types, the 'params' attr is actually just a scalar
675
        # value representing the data we need to serialize as a boolean.
676
        # It will either be 'true' or 'false'
677 11
        node = ElementTree.SubElement(xmlnode, name)
678 11
        if params:
679 11
            str_value = 'true'
680
        else:
681 11
            str_value = 'false'
682 11
        node.text = str_value
683

684 11
    def _serialize_type_blob(self, xmlnode, params, shape, name):
685 11
        node = ElementTree.SubElement(xmlnode, name)
686 11
        node.text = self._get_base64(params)
687

688 11
    def _serialize_type_timestamp(self, xmlnode, params, shape, name):
689 11
        node = ElementTree.SubElement(xmlnode, name)
690 11
        node.text = self._convert_timestamp_to_str(
691
            params, shape.serialization.get('timestampFormat'))
692

693 11
    def _default_serialize(self, xmlnode, params, shape, name):
694 11
        node = ElementTree.SubElement(xmlnode, name)
695 11
        node.text = six.text_type(params)
696

697

698 11
SERIALIZERS = {
699
    'ec2': EC2Serializer,
700
    'query': QuerySerializer,
701
    'json': JSONSerializer,
702
    'rest-json': RestJSONSerializer,
703
    'rest-xml': RestXMLSerializer,
704
}

Read our documentation on viewing source code .

Loading