Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/botocore/serialize.py: 22%
395 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
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"""Protocol input serializes.
15This module contains classes that implement input serialization
16for the various AWS protocol types.
18These classes essentially take user input, a model object that
19represents what the expected input should look like, and it returns
20a dictionary that contains the various parts of a request. A few
21high level design decisions:
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.
33Unicode
34-------
36The input to the serializers should be text (str/unicode), not bytes,
37with the exception of blob types. Those are assumed to be binary,
38and if a str/unicode type is passed in, it will be encoded as utf-8.
39"""
40import base64
41import calendar
42import datetime
43import json
44import re
45from xml.etree import ElementTree
47from botocore import validate
48from botocore.compat import formatdate
49from botocore.exceptions import ParamValidationError
50from botocore.utils import (
51 has_header,
52 is_json_value_header,
53 parse_to_aware_datetime,
54 percent_encode,
55)
57# From the spec, the default timestamp format if not specified is iso8601.
58DEFAULT_TIMESTAMP_FORMAT = 'iso8601'
59ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
60# Same as ISO8601, but with microsecond precision.
61ISO8601_MICRO = '%Y-%m-%dT%H:%M:%S.%fZ'
62HOST_PREFIX_RE = re.compile(r"^[A-Za-z0-9\.\-]+$")
65def create_serializer(protocol_name, include_validation=True):
66 # TODO: Unknown protocols.
67 serializer = SERIALIZERS[protocol_name]()
68 if include_validation:
69 validator = validate.ParamValidator()
70 serializer = validate.ParamValidationDecorator(validator, serializer)
71 return serializer
74class Serializer:
75 DEFAULT_METHOD = 'POST'
76 # Clients can change this to a different MutableMapping
77 # (i.e OrderedDict) if they want. This is used in the
78 # compliance test to match the hash ordering used in the
79 # tests.
80 MAP_TYPE = dict
81 DEFAULT_ENCODING = 'utf-8'
83 def serialize_to_request(self, parameters, operation_model):
84 """Serialize parameters into an HTTP request.
86 This method takes user provided parameters and a shape
87 model and serializes the parameters to an HTTP request.
88 More specifically, this method returns information about
89 parts of the HTTP request, it does not enforce a particular
90 interface or standard for an HTTP request. It instead returns
91 a dictionary of:
93 * 'url_path'
94 * 'host_prefix'
95 * 'query_string'
96 * 'headers'
97 * 'body'
98 * 'method'
100 It is then up to consumers to decide how to map this to a Request
101 object of their HTTP library of choice. Below is an example
102 return value::
104 {'body': {'Action': 'OperationName',
105 'Bar': 'val2',
106 'Foo': 'val1',
107 'Version': '2014-01-01'},
108 'headers': {},
109 'method': 'POST',
110 'query_string': '',
111 'host_prefix': 'value.',
112 'url_path': '/'}
114 :param parameters: The dictionary input parameters for the
115 operation (i.e the user input).
116 :param operation_model: The OperationModel object that describes
117 the operation.
118 """
119 raise NotImplementedError("serialize_to_request")
121 def _create_default_request(self):
122 # Creates a boilerplate default request dict that subclasses
123 # can use as a starting point.
124 serialized = {
125 'url_path': '/',
126 'query_string': '',
127 'method': self.DEFAULT_METHOD,
128 'headers': {},
129 # An empty body is represented as an empty byte string.
130 'body': b'',
131 }
132 return serialized
134 # Some extra utility methods subclasses can use.
136 def _timestamp_iso8601(self, value):
137 if value.microsecond > 0:
138 timestamp_format = ISO8601_MICRO
139 else:
140 timestamp_format = ISO8601
141 return value.strftime(timestamp_format)
143 def _timestamp_unixtimestamp(self, value):
144 return int(calendar.timegm(value.timetuple()))
146 def _timestamp_rfc822(self, value):
147 if isinstance(value, datetime.datetime):
148 value = self._timestamp_unixtimestamp(value)
149 return formatdate(value, usegmt=True)
151 def _convert_timestamp_to_str(self, value, timestamp_format=None):
152 if timestamp_format is None:
153 timestamp_format = self.TIMESTAMP_FORMAT
154 timestamp_format = timestamp_format.lower()
155 datetime_obj = parse_to_aware_datetime(value)
156 converter = getattr(self, f'_timestamp_{timestamp_format}')
157 final_value = converter(datetime_obj)
158 return final_value
160 def _get_serialized_name(self, shape, default_name):
161 # Returns the serialized name for the shape if it exists.
162 # Otherwise it will return the passed in default_name.
163 return shape.serialization.get('name', default_name)
165 def _get_base64(self, value):
166 # Returns the base64-encoded version of value, handling
167 # both strings and bytes. The returned value is a string
168 # via the default encoding.
169 if isinstance(value, str):
170 value = value.encode(self.DEFAULT_ENCODING)
171 return base64.b64encode(value).strip().decode(self.DEFAULT_ENCODING)
173 def _expand_host_prefix(self, parameters, operation_model):
174 operation_endpoint = operation_model.endpoint
175 if (
176 operation_endpoint is None
177 or 'hostPrefix' not in operation_endpoint
178 ):
179 return None
181 host_prefix_expression = operation_endpoint['hostPrefix']
182 input_members = operation_model.input_shape.members
183 host_labels = [
184 member
185 for member, shape in input_members.items()
186 if shape.serialization.get('hostLabel')
187 ]
188 format_kwargs = {}
189 bad_labels = []
190 for name in host_labels:
191 param = parameters[name]
192 if not HOST_PREFIX_RE.match(param):
193 bad_labels.append(name)
194 format_kwargs[name] = param
195 if bad_labels:
196 raise ParamValidationError(
197 report=(
198 f"Invalid value for parameter(s): {', '.join(bad_labels)}. "
199 "Must contain only alphanumeric characters, hyphen, "
200 "or period."
201 )
202 )
203 return host_prefix_expression.format(**format_kwargs)
206class QuerySerializer(Serializer):
207 TIMESTAMP_FORMAT = 'iso8601'
209 def serialize_to_request(self, parameters, operation_model):
210 shape = operation_model.input_shape
211 serialized = self._create_default_request()
212 serialized['method'] = operation_model.http.get(
213 'method', self.DEFAULT_METHOD
214 )
215 serialized['headers'] = {
216 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
217 }
218 # The query serializer only deals with body params so
219 # that's what we hand off the _serialize_* methods.
220 body_params = self.MAP_TYPE()
221 body_params['Action'] = operation_model.name
222 body_params['Version'] = operation_model.metadata['apiVersion']
223 if shape is not None:
224 self._serialize(body_params, parameters, shape)
225 serialized['body'] = body_params
227 host_prefix = self._expand_host_prefix(parameters, operation_model)
228 if host_prefix is not None:
229 serialized['host_prefix'] = host_prefix
231 return serialized
233 def _serialize(self, serialized, value, shape, prefix=''):
234 # serialized: The dict that is incrementally added to with the
235 # final serialized parameters.
236 # value: The current user input value.
237 # shape: The shape object that describes the structure of the
238 # input.
239 # prefix: The incrementally built up prefix for the serialized
240 # key (i.e Foo.bar.members.1).
241 method = getattr(
242 self,
243 f'_serialize_type_{shape.type_name}',
244 self._default_serialize,
245 )
246 method(serialized, value, shape, prefix=prefix)
248 def _serialize_type_structure(self, serialized, value, shape, prefix=''):
249 members = shape.members
250 for key, value in value.items():
251 member_shape = members[key]
252 member_prefix = self._get_serialized_name(member_shape, key)
253 if prefix:
254 member_prefix = f'{prefix}.{member_prefix}'
255 self._serialize(serialized, value, member_shape, member_prefix)
257 def _serialize_type_list(self, serialized, value, shape, prefix=''):
258 if not value:
259 # The query protocol serializes empty lists.
260 serialized[prefix] = ''
261 return
262 if self._is_shape_flattened(shape):
263 list_prefix = prefix
264 if shape.member.serialization.get('name'):
265 name = self._get_serialized_name(shape.member, default_name='')
266 # Replace '.Original' with '.{name}'.
267 list_prefix = '.'.join(prefix.split('.')[:-1] + [name])
268 else:
269 list_name = shape.member.serialization.get('name', 'member')
270 list_prefix = f'{prefix}.{list_name}'
271 for i, element in enumerate(value, 1):
272 element_prefix = f'{list_prefix}.{i}'
273 element_shape = shape.member
274 self._serialize(serialized, element, element_shape, element_prefix)
276 def _serialize_type_map(self, serialized, value, shape, prefix=''):
277 if self._is_shape_flattened(shape):
278 full_prefix = prefix
279 else:
280 full_prefix = '%s.entry' % prefix
281 template = full_prefix + '.{i}.{suffix}'
282 key_shape = shape.key
283 value_shape = shape.value
284 key_suffix = self._get_serialized_name(key_shape, default_name='key')
285 value_suffix = self._get_serialized_name(value_shape, 'value')
286 for i, key in enumerate(value, 1):
287 key_prefix = template.format(i=i, suffix=key_suffix)
288 value_prefix = template.format(i=i, suffix=value_suffix)
289 self._serialize(serialized, key, key_shape, key_prefix)
290 self._serialize(serialized, value[key], value_shape, value_prefix)
292 def _serialize_type_blob(self, serialized, value, shape, prefix=''):
293 # Blob args must be base64 encoded.
294 serialized[prefix] = self._get_base64(value)
296 def _serialize_type_timestamp(self, serialized, value, shape, prefix=''):
297 serialized[prefix] = self._convert_timestamp_to_str(
298 value, shape.serialization.get('timestampFormat')
299 )
301 def _serialize_type_boolean(self, serialized, value, shape, prefix=''):
302 if value:
303 serialized[prefix] = 'true'
304 else:
305 serialized[prefix] = 'false'
307 def _default_serialize(self, serialized, value, shape, prefix=''):
308 serialized[prefix] = value
310 def _is_shape_flattened(self, shape):
311 return shape.serialization.get('flattened')
314class EC2Serializer(QuerySerializer):
315 """EC2 specific customizations to the query protocol serializers.
317 The EC2 model is almost, but not exactly, similar to the query protocol
318 serializer. This class encapsulates those differences. The model
319 will have be marked with a ``protocol`` of ``ec2``, so you don't need
320 to worry about wiring this class up correctly.
322 """
324 def _get_serialized_name(self, shape, default_name):
325 # Returns the serialized name for the shape if it exists.
326 # Otherwise it will return the passed in default_name.
327 if 'queryName' in shape.serialization:
328 return shape.serialization['queryName']
329 elif 'name' in shape.serialization:
330 # A locationName is always capitalized
331 # on input for the ec2 protocol.
332 name = shape.serialization['name']
333 return name[0].upper() + name[1:]
334 else:
335 return default_name
337 def _serialize_type_list(self, serialized, value, shape, prefix=''):
338 for i, element in enumerate(value, 1):
339 element_prefix = f'{prefix}.{i}'
340 element_shape = shape.member
341 self._serialize(serialized, element, element_shape, element_prefix)
344class JSONSerializer(Serializer):
345 TIMESTAMP_FORMAT = 'unixtimestamp'
347 def serialize_to_request(self, parameters, operation_model):
348 target = '{}.{}'.format(
349 operation_model.metadata['targetPrefix'],
350 operation_model.name,
351 )
352 json_version = operation_model.metadata['jsonVersion']
353 serialized = self._create_default_request()
354 serialized['method'] = operation_model.http.get(
355 'method', self.DEFAULT_METHOD
356 )
357 serialized['headers'] = {
358 'X-Amz-Target': target,
359 'Content-Type': 'application/x-amz-json-%s' % json_version,
360 }
361 body = self.MAP_TYPE()
362 input_shape = operation_model.input_shape
363 if input_shape is not None:
364 self._serialize(body, parameters, input_shape)
365 serialized['body'] = json.dumps(body).encode(self.DEFAULT_ENCODING)
367 host_prefix = self._expand_host_prefix(parameters, operation_model)
368 if host_prefix is not None:
369 serialized['host_prefix'] = host_prefix
371 return serialized
373 def _serialize(self, serialized, value, shape, key=None):
374 method = getattr(
375 self,
376 '_serialize_type_%s' % shape.type_name,
377 self._default_serialize,
378 )
379 method(serialized, value, shape, key)
381 def _serialize_type_structure(self, serialized, value, shape, key):
382 if shape.is_document_type:
383 serialized[key] = value
384 else:
385 if key is not None:
386 # If a key is provided, this is a result of a recursive
387 # call so we need to add a new child dict as the value
388 # of the passed in serialized dict. We'll then add
389 # all the structure members as key/vals in the new serialized
390 # dictionary we just created.
391 new_serialized = self.MAP_TYPE()
392 serialized[key] = new_serialized
393 serialized = new_serialized
394 members = shape.members
395 for member_key, member_value in value.items():
396 member_shape = members[member_key]
397 if 'name' in member_shape.serialization:
398 member_key = member_shape.serialization['name']
399 self._serialize(
400 serialized, member_value, member_shape, member_key
401 )
403 def _serialize_type_map(self, serialized, value, shape, key):
404 map_obj = self.MAP_TYPE()
405 serialized[key] = map_obj
406 for sub_key, sub_value in value.items():
407 self._serialize(map_obj, sub_value, shape.value, sub_key)
409 def _serialize_type_list(self, serialized, value, shape, key):
410 list_obj = []
411 serialized[key] = list_obj
412 for list_item in value:
413 wrapper = {}
414 # The JSON list serialization is the only case where we aren't
415 # setting a key on a dict. We handle this by using
416 # a __current__ key on a wrapper dict to serialize each
417 # list item before appending it to the serialized list.
418 self._serialize(wrapper, list_item, shape.member, "__current__")
419 list_obj.append(wrapper["__current__"])
421 def _default_serialize(self, serialized, value, shape, key):
422 serialized[key] = value
424 def _serialize_type_timestamp(self, serialized, value, shape, key):
425 serialized[key] = self._convert_timestamp_to_str(
426 value, shape.serialization.get('timestampFormat')
427 )
429 def _serialize_type_blob(self, serialized, value, shape, key):
430 serialized[key] = self._get_base64(value)
433class BaseRestSerializer(Serializer):
434 """Base class for rest protocols.
436 The only variance between the various rest protocols is the
437 way that the body is serialized. All other aspects (headers, uri, etc.)
438 are the same and logic for serializing those aspects lives here.
440 Subclasses must implement the ``_serialize_body_params`` method.
442 """
444 QUERY_STRING_TIMESTAMP_FORMAT = 'iso8601'
445 HEADER_TIMESTAMP_FORMAT = 'rfc822'
446 # This is a list of known values for the "location" key in the
447 # serialization dict. The location key tells us where on the request
448 # to put the serialized value.
449 KNOWN_LOCATIONS = ['uri', 'querystring', 'header', 'headers']
451 def serialize_to_request(self, parameters, operation_model):
452 serialized = self._create_default_request()
453 serialized['method'] = operation_model.http.get(
454 'method', self.DEFAULT_METHOD
455 )
456 shape = operation_model.input_shape
457 if shape is None:
458 serialized['url_path'] = operation_model.http['requestUri']
459 return serialized
460 shape_members = shape.members
461 # While the ``serialized`` key holds the final serialized request
462 # data, we need interim dicts for the various locations of the
463 # request. We need this for the uri_path_kwargs and the
464 # query_string_kwargs because they are templated, so we need
465 # to gather all the needed data for the string template,
466 # then we render the template. The body_kwargs is needed
467 # because once we've collected them all, we run them through
468 # _serialize_body_params, which for rest-json, creates JSON,
469 # and for rest-xml, will create XML. This is what the
470 # ``partitioned`` dict below is for.
471 partitioned = {
472 'uri_path_kwargs': self.MAP_TYPE(),
473 'query_string_kwargs': self.MAP_TYPE(),
474 'body_kwargs': self.MAP_TYPE(),
475 'headers': self.MAP_TYPE(),
476 }
477 for param_name, param_value in parameters.items():
478 if param_value is None:
479 # Don't serialize any parameter with a None value.
480 continue
481 self._partition_parameters(
482 partitioned, param_name, param_value, shape_members
483 )
484 serialized['url_path'] = self._render_uri_template(
485 operation_model.http['requestUri'], partitioned['uri_path_kwargs']
486 )
488 if 'authPath' in operation_model.http:
489 serialized['auth_path'] = self._render_uri_template(
490 operation_model.http['authPath'],
491 partitioned['uri_path_kwargs'],
492 )
493 # Note that we lean on the http implementation to handle the case
494 # where the requestUri path already has query parameters.
495 # The bundled http client, requests, already supports this.
496 serialized['query_string'] = partitioned['query_string_kwargs']
497 if partitioned['headers']:
498 serialized['headers'] = partitioned['headers']
499 self._serialize_payload(
500 partitioned, parameters, serialized, shape, shape_members
501 )
502 self._serialize_content_type(serialized, shape, shape_members)
504 host_prefix = self._expand_host_prefix(parameters, operation_model)
505 if host_prefix is not None:
506 serialized['host_prefix'] = host_prefix
508 return serialized
510 def _render_uri_template(self, uri_template, params):
511 # We need to handle two cases::
512 #
513 # /{Bucket}/foo
514 # /{Key+}/bar
515 # A label ending with '+' is greedy. There can only
516 # be one greedy key.
517 encoded_params = {}
518 for template_param in re.findall(r'{(.*?)}', uri_template):
519 if template_param.endswith('+'):
520 encoded_params[template_param] = percent_encode(
521 params[template_param[:-1]], safe='/~'
522 )
523 else:
524 encoded_params[template_param] = percent_encode(
525 params[template_param]
526 )
527 return uri_template.format(**encoded_params)
529 def _serialize_payload(
530 self, partitioned, parameters, serialized, shape, shape_members
531 ):
532 # partitioned - The user input params partitioned by location.
533 # parameters - The user input params.
534 # serialized - The final serialized request dict.
535 # shape - Describes the expected input shape
536 # shape_members - The members of the input struct shape
537 payload_member = shape.serialization.get('payload')
538 if self._has_streaming_payload(payload_member, shape_members):
539 # If it's streaming, then the body is just the
540 # value of the payload.
541 body_payload = parameters.get(payload_member, b'')
542 body_payload = self._encode_payload(body_payload)
543 serialized['body'] = body_payload
544 elif payload_member is not None:
545 # If there's a payload member, we serialized that
546 # member to they body.
547 body_params = parameters.get(payload_member)
548 if body_params is not None:
549 serialized['body'] = self._serialize_body_params(
550 body_params, shape_members[payload_member]
551 )
552 else:
553 serialized['body'] = self._serialize_empty_body()
554 elif partitioned['body_kwargs']:
555 serialized['body'] = self._serialize_body_params(
556 partitioned['body_kwargs'], shape
557 )
558 elif self._requires_empty_body(shape):
559 serialized['body'] = self._serialize_empty_body()
561 def _serialize_empty_body(self):
562 return b''
564 def _serialize_content_type(self, serialized, shape, shape_members):
565 """
566 Some protocols require varied Content-Type headers
567 depending on user input. This allows subclasses to apply
568 this conditionally.
569 """
570 pass
572 def _requires_empty_body(self, shape):
573 """
574 Some protocols require a specific body to represent an empty
575 payload. This allows subclasses to apply this conditionally.
576 """
577 return False
579 def _has_streaming_payload(self, payload, shape_members):
580 """Determine if payload is streaming (a blob or string)."""
581 return payload is not None and shape_members[payload].type_name in (
582 'blob',
583 'string',
584 )
586 def _encode_payload(self, body):
587 if isinstance(body, str):
588 return body.encode(self.DEFAULT_ENCODING)
589 return body
591 def _partition_parameters(
592 self, partitioned, param_name, param_value, shape_members
593 ):
594 # This takes the user provided input parameter (``param``)
595 # and figures out where they go in the request dict.
596 # Some params are HTTP headers, some are used in the URI, some
597 # are in the request body. This method deals with this.
598 member = shape_members[param_name]
599 location = member.serialization.get('location')
600 key_name = member.serialization.get('name', param_name)
601 if location == 'uri':
602 partitioned['uri_path_kwargs'][key_name] = param_value
603 elif location == 'querystring':
604 if isinstance(param_value, dict):
605 partitioned['query_string_kwargs'].update(param_value)
606 elif isinstance(param_value, bool):
607 bool_str = str(param_value).lower()
608 partitioned['query_string_kwargs'][key_name] = bool_str
609 elif member.type_name == 'timestamp':
610 timestamp_format = member.serialization.get(
611 'timestampFormat', self.QUERY_STRING_TIMESTAMP_FORMAT
612 )
613 timestamp = self._convert_timestamp_to_str(
614 param_value, timestamp_format
615 )
616 partitioned['query_string_kwargs'][key_name] = timestamp
617 else:
618 partitioned['query_string_kwargs'][key_name] = param_value
619 elif location == 'header':
620 shape = shape_members[param_name]
621 if not param_value and shape.type_name == 'list':
622 # Empty lists should not be set on the headers
623 return
624 value = self._convert_header_value(shape, param_value)
625 partitioned['headers'][key_name] = str(value)
626 elif location == 'headers':
627 # 'headers' is a bit of an oddball. The ``key_name``
628 # is actually really a prefix for the header names:
629 header_prefix = key_name
630 # The value provided by the user is a dict so we'll be
631 # creating multiple header key/val pairs. The key
632 # name to use for each header is the header_prefix (``key_name``)
633 # plus the key provided by the user.
634 self._do_serialize_header_map(
635 header_prefix, partitioned['headers'], param_value
636 )
637 else:
638 partitioned['body_kwargs'][param_name] = param_value
640 def _do_serialize_header_map(self, header_prefix, headers, user_input):
641 for key, val in user_input.items():
642 full_key = header_prefix + key
643 headers[full_key] = val
645 def _serialize_body_params(self, params, shape):
646 raise NotImplementedError('_serialize_body_params')
648 def _convert_header_value(self, shape, value):
649 if shape.type_name == 'timestamp':
650 datetime_obj = parse_to_aware_datetime(value)
651 timestamp = calendar.timegm(datetime_obj.utctimetuple())
652 timestamp_format = shape.serialization.get(
653 'timestampFormat', self.HEADER_TIMESTAMP_FORMAT
654 )
655 return self._convert_timestamp_to_str(timestamp, timestamp_format)
656 elif shape.type_name == 'list':
657 converted_value = [
658 self._convert_header_value(shape.member, v)
659 for v in value
660 if v is not None
661 ]
662 return ",".join(converted_value)
663 elif is_json_value_header(shape):
664 # Serialize with no spaces after separators to save space in
665 # the header.
666 return self._get_base64(json.dumps(value, separators=(',', ':')))
667 else:
668 return value
671class RestJSONSerializer(BaseRestSerializer, JSONSerializer):
672 def _serialize_empty_body(self):
673 return b'{}'
675 def _requires_empty_body(self, shape):
676 """
677 Serialize an empty JSON object whenever the shape has
678 members not targeting a location.
679 """
680 for member, val in shape.members.items():
681 if 'location' not in val.serialization:
682 return True
683 return False
685 def _serialize_content_type(self, serialized, shape, shape_members):
686 """Set Content-Type to application/json for all structured bodies."""
687 payload = shape.serialization.get('payload')
688 if self._has_streaming_payload(payload, shape_members):
689 # Don't apply content-type to streaming bodies
690 return
692 has_body = serialized['body'] != b''
693 has_content_type = has_header('Content-Type', serialized['headers'])
694 if has_body and not has_content_type:
695 serialized['headers']['Content-Type'] = 'application/json'
697 def _serialize_body_params(self, params, shape):
698 serialized_body = self.MAP_TYPE()
699 self._serialize(serialized_body, params, shape)
700 return json.dumps(serialized_body).encode(self.DEFAULT_ENCODING)
703class RestXMLSerializer(BaseRestSerializer):
704 TIMESTAMP_FORMAT = 'iso8601'
706 def _serialize_body_params(self, params, shape):
707 root_name = shape.serialization['name']
708 pseudo_root = ElementTree.Element('')
709 self._serialize(shape, params, pseudo_root, root_name)
710 real_root = list(pseudo_root)[0]
711 return ElementTree.tostring(real_root, encoding=self.DEFAULT_ENCODING)
713 def _serialize(self, shape, params, xmlnode, name):
714 method = getattr(
715 self,
716 '_serialize_type_%s' % shape.type_name,
717 self._default_serialize,
718 )
719 method(xmlnode, params, shape, name)
721 def _serialize_type_structure(self, xmlnode, params, shape, name):
722 structure_node = ElementTree.SubElement(xmlnode, name)
724 if 'xmlNamespace' in shape.serialization:
725 namespace_metadata = shape.serialization['xmlNamespace']
726 attribute_name = 'xmlns'
727 if namespace_metadata.get('prefix'):
728 attribute_name += ':%s' % namespace_metadata['prefix']
729 structure_node.attrib[attribute_name] = namespace_metadata['uri']
730 for key, value in params.items():
731 member_shape = shape.members[key]
732 member_name = member_shape.serialization.get('name', key)
733 # We need to special case member shapes that are marked as an
734 # xmlAttribute. Rather than serializing into an XML child node,
735 # we instead serialize the shape to an XML attribute of the
736 # *current* node.
737 if value is None:
738 # Don't serialize any param whose value is None.
739 return
740 if member_shape.serialization.get('xmlAttribute'):
741 # xmlAttributes must have a serialization name.
742 xml_attribute_name = member_shape.serialization['name']
743 structure_node.attrib[xml_attribute_name] = value
744 continue
745 self._serialize(member_shape, value, structure_node, member_name)
747 def _serialize_type_list(self, xmlnode, params, shape, name):
748 member_shape = shape.member
749 if shape.serialization.get('flattened'):
750 element_name = name
751 list_node = xmlnode
752 else:
753 element_name = member_shape.serialization.get('name', 'member')
754 list_node = ElementTree.SubElement(xmlnode, name)
755 for item in params:
756 self._serialize(member_shape, item, list_node, element_name)
758 def _serialize_type_map(self, xmlnode, params, shape, name):
759 # Given the ``name`` of MyMap, and input of {"key1": "val1"}
760 # we serialize this as:
761 # <MyMap>
762 # <entry>
763 # <key>key1</key>
764 # <value>val1</value>
765 # </entry>
766 # </MyMap>
767 node = ElementTree.SubElement(xmlnode, name)
768 # TODO: handle flattened maps.
769 for key, value in params.items():
770 entry_node = ElementTree.SubElement(node, 'entry')
771 key_name = self._get_serialized_name(shape.key, default_name='key')
772 val_name = self._get_serialized_name(
773 shape.value, default_name='value'
774 )
775 self._serialize(shape.key, key, entry_node, key_name)
776 self._serialize(shape.value, value, entry_node, val_name)
778 def _serialize_type_boolean(self, xmlnode, params, shape, name):
779 # For scalar types, the 'params' attr is actually just a scalar
780 # value representing the data we need to serialize as a boolean.
781 # It will either be 'true' or 'false'
782 node = ElementTree.SubElement(xmlnode, name)
783 if params:
784 str_value = 'true'
785 else:
786 str_value = 'false'
787 node.text = str_value
789 def _serialize_type_blob(self, xmlnode, params, shape, name):
790 node = ElementTree.SubElement(xmlnode, name)
791 node.text = self._get_base64(params)
793 def _serialize_type_timestamp(self, xmlnode, params, shape, name):
794 node = ElementTree.SubElement(xmlnode, name)
795 node.text = self._convert_timestamp_to_str(
796 params, shape.serialization.get('timestampFormat')
797 )
799 def _default_serialize(self, xmlnode, params, shape, name):
800 node = ElementTree.SubElement(xmlnode, name)
801 node.text = str(params)
804SERIALIZERS = {
805 'ec2': EC2Serializer,
806 'query': QuerySerializer,
807 'json': JSONSerializer,
808 'rest-json': RestJSONSerializer,
809 'rest-xml': RestXMLSerializer,
810}