Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/botocore/serialize.py: 23%
385 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:03 +0000
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:03 +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.utils import (
50 has_header,
51 is_json_value_header,
52 parse_to_aware_datetime,
53 percent_encode,
54)
56# From the spec, the default timestamp format if not specified is iso8601.
57DEFAULT_TIMESTAMP_FORMAT = 'iso8601'
58ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
59# Same as ISO8601, but with microsecond precision.
60ISO8601_MICRO = '%Y-%m-%dT%H:%M:%S.%fZ'
63def create_serializer(protocol_name, include_validation=True):
64 # TODO: Unknown protocols.
65 serializer = SERIALIZERS[protocol_name]()
66 if include_validation:
67 validator = validate.ParamValidator()
68 serializer = validate.ParamValidationDecorator(validator, serializer)
69 return serializer
72class Serializer:
73 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 MAP_TYPE = dict
79 DEFAULT_ENCODING = 'utf-8'
81 def serialize_to_request(self, parameters, operation_model):
82 """Serialize parameters into an HTTP request.
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:
91 * 'url_path'
92 * 'host_prefix'
93 * 'query_string'
94 * 'headers'
95 * 'body'
96 * 'method'
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::
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': '/'}
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")
119 def _create_default_request(self):
120 # Creates a boilerplate default request dict that subclasses
121 # can use as a starting point.
122 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 return serialized
132 # Some extra utility methods subclasses can use.
134 def _timestamp_iso8601(self, value):
135 if value.microsecond > 0:
136 timestamp_format = ISO8601_MICRO
137 else:
138 timestamp_format = ISO8601
139 return value.strftime(timestamp_format)
141 def _timestamp_unixtimestamp(self, value):
142 return int(calendar.timegm(value.timetuple()))
144 def _timestamp_rfc822(self, value):
145 if isinstance(value, datetime.datetime):
146 value = self._timestamp_unixtimestamp(value)
147 return formatdate(value, usegmt=True)
149 def _convert_timestamp_to_str(self, value, timestamp_format=None):
150 if timestamp_format is None:
151 timestamp_format = self.TIMESTAMP_FORMAT
152 timestamp_format = timestamp_format.lower()
153 datetime_obj = parse_to_aware_datetime(value)
154 converter = getattr(self, f'_timestamp_{timestamp_format}')
155 final_value = converter(datetime_obj)
156 return final_value
158 def _get_serialized_name(self, shape, default_name):
159 # Returns the serialized name for the shape if it exists.
160 # Otherwise it will return the passed in default_name.
161 return shape.serialization.get('name', default_name)
163 def _get_base64(self, value):
164 # Returns the base64-encoded version of value, handling
165 # both strings and bytes. The returned value is a string
166 # via the default encoding.
167 if isinstance(value, str):
168 value = value.encode(self.DEFAULT_ENCODING)
169 return base64.b64encode(value).strip().decode(self.DEFAULT_ENCODING)
171 def _expand_host_prefix(self, parameters, operation_model):
172 operation_endpoint = operation_model.endpoint
173 if (
174 operation_endpoint is None
175 or 'hostPrefix' not in operation_endpoint
176 ):
177 return None
179 host_prefix_expression = operation_endpoint['hostPrefix']
180 input_members = operation_model.input_shape.members
181 host_labels = [
182 member
183 for member, shape in input_members.items()
184 if shape.serialization.get('hostLabel')
185 ]
186 format_kwargs = {name: parameters[name] for name in host_labels}
188 return host_prefix_expression.format(**format_kwargs)
191class QuerySerializer(Serializer):
193 TIMESTAMP_FORMAT = 'iso8601'
195 def serialize_to_request(self, parameters, operation_model):
196 shape = operation_model.input_shape
197 serialized = self._create_default_request()
198 serialized['method'] = operation_model.http.get(
199 'method', self.DEFAULT_METHOD
200 )
201 serialized['headers'] = {
202 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
203 }
204 # The query serializer only deals with body params so
205 # that's what we hand off the _serialize_* methods.
206 body_params = self.MAP_TYPE()
207 body_params['Action'] = operation_model.name
208 body_params['Version'] = operation_model.metadata['apiVersion']
209 if shape is not None:
210 self._serialize(body_params, parameters, shape)
211 serialized['body'] = body_params
213 host_prefix = self._expand_host_prefix(parameters, operation_model)
214 if host_prefix is not None:
215 serialized['host_prefix'] = host_prefix
217 return serialized
219 def _serialize(self, serialized, value, shape, prefix=''):
220 # serialized: The dict that is incrementally added to with the
221 # final serialized parameters.
222 # value: The current user input value.
223 # shape: The shape object that describes the structure of the
224 # input.
225 # prefix: The incrementally built up prefix for the serialized
226 # key (i.e Foo.bar.members.1).
227 method = getattr(
228 self,
229 f'_serialize_type_{shape.type_name}',
230 self._default_serialize,
231 )
232 method(serialized, value, shape, prefix=prefix)
234 def _serialize_type_structure(self, serialized, value, shape, prefix=''):
235 members = shape.members
236 for key, value in value.items():
237 member_shape = members[key]
238 member_prefix = self._get_serialized_name(member_shape, key)
239 if prefix:
240 member_prefix = f'{prefix}.{member_prefix}'
241 self._serialize(serialized, value, member_shape, member_prefix)
243 def _serialize_type_list(self, serialized, value, shape, prefix=''):
244 if not value:
245 # The query protocol serializes empty lists.
246 serialized[prefix] = ''
247 return
248 if self._is_shape_flattened(shape):
249 list_prefix = prefix
250 if shape.member.serialization.get('name'):
251 name = self._get_serialized_name(shape.member, default_name='')
252 # Replace '.Original' with '.{name}'.
253 list_prefix = '.'.join(prefix.split('.')[:-1] + [name])
254 else:
255 list_name = shape.member.serialization.get('name', 'member')
256 list_prefix = f'{prefix}.{list_name}'
257 for i, element in enumerate(value, 1):
258 element_prefix = f'{list_prefix}.{i}'
259 element_shape = shape.member
260 self._serialize(serialized, element, element_shape, element_prefix)
262 def _serialize_type_map(self, serialized, value, shape, prefix=''):
263 if self._is_shape_flattened(shape):
264 full_prefix = prefix
265 else:
266 full_prefix = '%s.entry' % prefix
267 template = full_prefix + '.{i}.{suffix}'
268 key_shape = shape.key
269 value_shape = shape.value
270 key_suffix = self._get_serialized_name(key_shape, default_name='key')
271 value_suffix = self._get_serialized_name(value_shape, 'value')
272 for i, key in enumerate(value, 1):
273 key_prefix = template.format(i=i, suffix=key_suffix)
274 value_prefix = template.format(i=i, suffix=value_suffix)
275 self._serialize(serialized, key, key_shape, key_prefix)
276 self._serialize(serialized, value[key], value_shape, value_prefix)
278 def _serialize_type_blob(self, serialized, value, shape, prefix=''):
279 # Blob args must be base64 encoded.
280 serialized[prefix] = self._get_base64(value)
282 def _serialize_type_timestamp(self, serialized, value, shape, prefix=''):
283 serialized[prefix] = self._convert_timestamp_to_str(
284 value, shape.serialization.get('timestampFormat')
285 )
287 def _serialize_type_boolean(self, serialized, value, shape, prefix=''):
288 if value:
289 serialized[prefix] = 'true'
290 else:
291 serialized[prefix] = 'false'
293 def _default_serialize(self, serialized, value, shape, prefix=''):
294 serialized[prefix] = value
296 def _is_shape_flattened(self, shape):
297 return shape.serialization.get('flattened')
300class EC2Serializer(QuerySerializer):
301 """EC2 specific customizations to the query protocol serializers.
303 The EC2 model is almost, but not exactly, similar to the query protocol
304 serializer. This class encapsulates those differences. The model
305 will have be marked with a ``protocol`` of ``ec2``, so you don't need
306 to worry about wiring this class up correctly.
308 """
310 def _get_serialized_name(self, shape, default_name):
311 # Returns the serialized name for the shape if it exists.
312 # Otherwise it will return the passed in default_name.
313 if 'queryName' in shape.serialization:
314 return shape.serialization['queryName']
315 elif 'name' in shape.serialization:
316 # A locationName is always capitalized
317 # on input for the ec2 protocol.
318 name = shape.serialization['name']
319 return name[0].upper() + name[1:]
320 else:
321 return default_name
323 def _serialize_type_list(self, serialized, value, shape, prefix=''):
324 for i, element in enumerate(value, 1):
325 element_prefix = f'{prefix}.{i}'
326 element_shape = shape.member
327 self._serialize(serialized, element, element_shape, element_prefix)
330class JSONSerializer(Serializer):
331 TIMESTAMP_FORMAT = 'unixtimestamp'
333 def serialize_to_request(self, parameters, operation_model):
334 target = '{}.{}'.format(
335 operation_model.metadata['targetPrefix'],
336 operation_model.name,
337 )
338 json_version = operation_model.metadata['jsonVersion']
339 serialized = self._create_default_request()
340 serialized['method'] = operation_model.http.get(
341 'method', self.DEFAULT_METHOD
342 )
343 serialized['headers'] = {
344 'X-Amz-Target': target,
345 'Content-Type': 'application/x-amz-json-%s' % json_version,
346 }
347 body = self.MAP_TYPE()
348 input_shape = operation_model.input_shape
349 if input_shape is not None:
350 self._serialize(body, parameters, input_shape)
351 serialized['body'] = json.dumps(body).encode(self.DEFAULT_ENCODING)
353 host_prefix = self._expand_host_prefix(parameters, operation_model)
354 if host_prefix is not None:
355 serialized['host_prefix'] = host_prefix
357 return serialized
359 def _serialize(self, serialized, value, shape, key=None):
360 method = getattr(
361 self,
362 '_serialize_type_%s' % shape.type_name,
363 self._default_serialize,
364 )
365 method(serialized, value, shape, key)
367 def _serialize_type_structure(self, serialized, value, shape, key):
368 if shape.is_document_type:
369 serialized[key] = value
370 else:
371 if key is not None:
372 # If a key is provided, this is a result of a recursive
373 # call so we need to add a new child dict as the value
374 # of the passed in serialized dict. We'll then add
375 # all the structure members as key/vals in the new serialized
376 # dictionary we just created.
377 new_serialized = self.MAP_TYPE()
378 serialized[key] = new_serialized
379 serialized = new_serialized
380 members = shape.members
381 for member_key, member_value in value.items():
382 member_shape = members[member_key]
383 if 'name' in member_shape.serialization:
384 member_key = member_shape.serialization['name']
385 self._serialize(
386 serialized, member_value, member_shape, member_key
387 )
389 def _serialize_type_map(self, serialized, value, shape, key):
390 map_obj = self.MAP_TYPE()
391 serialized[key] = map_obj
392 for sub_key, sub_value in value.items():
393 self._serialize(map_obj, sub_value, shape.value, sub_key)
395 def _serialize_type_list(self, serialized, value, shape, key):
396 list_obj = []
397 serialized[key] = list_obj
398 for list_item in value:
399 wrapper = {}
400 # The JSON list serialization is the only case where we aren't
401 # setting a key on a dict. We handle this by using
402 # a __current__ key on a wrapper dict to serialize each
403 # list item before appending it to the serialized list.
404 self._serialize(wrapper, list_item, shape.member, "__current__")
405 list_obj.append(wrapper["__current__"])
407 def _default_serialize(self, serialized, value, shape, key):
408 serialized[key] = value
410 def _serialize_type_timestamp(self, serialized, value, shape, key):
411 serialized[key] = self._convert_timestamp_to_str(
412 value, shape.serialization.get('timestampFormat')
413 )
415 def _serialize_type_blob(self, serialized, value, shape, key):
416 serialized[key] = self._get_base64(value)
419class BaseRestSerializer(Serializer):
420 """Base class for rest protocols.
422 The only variance between the various rest protocols is the
423 way that the body is serialized. All other aspects (headers, uri, etc.)
424 are the same and logic for serializing those aspects lives here.
426 Subclasses must implement the ``_serialize_body_params`` method.
428 """
430 QUERY_STRING_TIMESTAMP_FORMAT = 'iso8601'
431 HEADER_TIMESTAMP_FORMAT = 'rfc822'
432 # This is a list of known values for the "location" key in the
433 # serialization dict. The location key tells us where on the request
434 # to put the serialized value.
435 KNOWN_LOCATIONS = ['uri', 'querystring', 'header', 'headers']
437 def serialize_to_request(self, parameters, operation_model):
438 serialized = self._create_default_request()
439 serialized['method'] = operation_model.http.get(
440 'method', self.DEFAULT_METHOD
441 )
442 shape = operation_model.input_shape
443 if shape is None:
444 serialized['url_path'] = operation_model.http['requestUri']
445 return serialized
446 shape_members = shape.members
447 # While the ``serialized`` key holds the final serialized request
448 # data, we need interim dicts for the various locations of the
449 # request. We need this for the uri_path_kwargs and the
450 # query_string_kwargs because they are templated, so we need
451 # to gather all the needed data for the string template,
452 # then we render the template. The body_kwargs is needed
453 # because once we've collected them all, we run them through
454 # _serialize_body_params, which for rest-json, creates JSON,
455 # and for rest-xml, will create XML. This is what the
456 # ``partitioned`` dict below is for.
457 partitioned = {
458 'uri_path_kwargs': self.MAP_TYPE(),
459 'query_string_kwargs': self.MAP_TYPE(),
460 'body_kwargs': self.MAP_TYPE(),
461 'headers': self.MAP_TYPE(),
462 }
463 for param_name, param_value in parameters.items():
464 if param_value is None:
465 # Don't serialize any parameter with a None value.
466 continue
467 self._partition_parameters(
468 partitioned, param_name, param_value, shape_members
469 )
470 serialized['url_path'] = self._render_uri_template(
471 operation_model.http['requestUri'], partitioned['uri_path_kwargs']
472 )
474 if 'authPath' in operation_model.http:
475 serialized['auth_path'] = self._render_uri_template(
476 operation_model.http['authPath'],
477 partitioned['uri_path_kwargs'],
478 )
479 # Note that we lean on the http implementation to handle the case
480 # where the requestUri path already has query parameters.
481 # The bundled http client, requests, already supports this.
482 serialized['query_string'] = partitioned['query_string_kwargs']
483 if partitioned['headers']:
484 serialized['headers'] = partitioned['headers']
485 self._serialize_payload(
486 partitioned, parameters, serialized, shape, shape_members
487 )
488 self._serialize_content_type(serialized, shape, shape_members)
490 host_prefix = self._expand_host_prefix(parameters, operation_model)
491 if host_prefix is not None:
492 serialized['host_prefix'] = host_prefix
494 return serialized
496 def _render_uri_template(self, uri_template, params):
497 # We need to handle two cases::
498 #
499 # /{Bucket}/foo
500 # /{Key+}/bar
501 # A label ending with '+' is greedy. There can only
502 # be one greedy key.
503 encoded_params = {}
504 for template_param in re.findall(r'{(.*?)}', uri_template):
505 if template_param.endswith('+'):
506 encoded_params[template_param] = percent_encode(
507 params[template_param[:-1]], safe='/~'
508 )
509 else:
510 encoded_params[template_param] = percent_encode(
511 params[template_param]
512 )
513 return uri_template.format(**encoded_params)
515 def _serialize_payload(
516 self, partitioned, parameters, serialized, shape, shape_members
517 ):
518 # partitioned - The user input params partitioned by location.
519 # parameters - The user input params.
520 # serialized - The final serialized request dict.
521 # shape - Describes the expected input shape
522 # shape_members - The members of the input struct shape
523 payload_member = shape.serialization.get('payload')
524 if self._has_streaming_payload(payload_member, shape_members):
525 # If it's streaming, then the body is just the
526 # value of the payload.
527 body_payload = parameters.get(payload_member, b'')
528 body_payload = self._encode_payload(body_payload)
529 serialized['body'] = body_payload
530 elif payload_member is not None:
531 # If there's a payload member, we serialized that
532 # member to they body.
533 body_params = parameters.get(payload_member)
534 if body_params is not None:
535 serialized['body'] = self._serialize_body_params(
536 body_params, shape_members[payload_member]
537 )
538 else:
539 serialized['body'] = self._serialize_empty_body()
540 elif partitioned['body_kwargs']:
541 serialized['body'] = self._serialize_body_params(
542 partitioned['body_kwargs'], shape
543 )
544 elif self._requires_empty_body(shape):
545 serialized['body'] = self._serialize_empty_body()
547 def _serialize_empty_body(self):
548 return b''
550 def _serialize_content_type(self, serialized, shape, shape_members):
551 """
552 Some protocols require varied Content-Type headers
553 depending on user input. This allows subclasses to apply
554 this conditionally.
555 """
556 pass
558 def _requires_empty_body(self, shape):
559 """
560 Some protocols require a specific body to represent an empty
561 payload. This allows subclasses to apply this conditionally.
562 """
563 return False
565 def _has_streaming_payload(self, payload, shape_members):
566 """Determine if payload is streaming (a blob or string)."""
567 return payload is not None and shape_members[payload].type_name in (
568 'blob',
569 'string',
570 )
572 def _encode_payload(self, body):
573 if isinstance(body, str):
574 return body.encode(self.DEFAULT_ENCODING)
575 return body
577 def _partition_parameters(
578 self, partitioned, param_name, param_value, shape_members
579 ):
580 # This takes the user provided input parameter (``param``)
581 # and figures out where they go in the request dict.
582 # Some params are HTTP headers, some are used in the URI, some
583 # are in the request body. This method deals with this.
584 member = shape_members[param_name]
585 location = member.serialization.get('location')
586 key_name = member.serialization.get('name', param_name)
587 if location == 'uri':
588 partitioned['uri_path_kwargs'][key_name] = param_value
589 elif location == 'querystring':
590 if isinstance(param_value, dict):
591 partitioned['query_string_kwargs'].update(param_value)
592 elif isinstance(param_value, bool):
593 bool_str = str(param_value).lower()
594 partitioned['query_string_kwargs'][key_name] = bool_str
595 elif member.type_name == 'timestamp':
596 timestamp_format = member.serialization.get(
597 'timestampFormat', self.QUERY_STRING_TIMESTAMP_FORMAT
598 )
599 timestamp = self._convert_timestamp_to_str(
600 param_value, timestamp_format
601 )
602 partitioned['query_string_kwargs'][key_name] = timestamp
603 else:
604 partitioned['query_string_kwargs'][key_name] = param_value
605 elif location == 'header':
606 shape = shape_members[param_name]
607 if not param_value and shape.type_name == 'list':
608 # Empty lists should not be set on the headers
609 return
610 value = self._convert_header_value(shape, param_value)
611 partitioned['headers'][key_name] = str(value)
612 elif location == 'headers':
613 # 'headers' is a bit of an oddball. The ``key_name``
614 # is actually really a prefix for the header names:
615 header_prefix = key_name
616 # The value provided by the user is a dict so we'll be
617 # creating multiple header key/val pairs. The key
618 # name to use for each header is the header_prefix (``key_name``)
619 # plus the key provided by the user.
620 self._do_serialize_header_map(
621 header_prefix, partitioned['headers'], param_value
622 )
623 else:
624 partitioned['body_kwargs'][param_name] = param_value
626 def _do_serialize_header_map(self, header_prefix, headers, user_input):
627 for key, val in user_input.items():
628 full_key = header_prefix + key
629 headers[full_key] = val
631 def _serialize_body_params(self, params, shape):
632 raise NotImplementedError('_serialize_body_params')
634 def _convert_header_value(self, shape, value):
635 if shape.type_name == 'timestamp':
636 datetime_obj = parse_to_aware_datetime(value)
637 timestamp = calendar.timegm(datetime_obj.utctimetuple())
638 timestamp_format = shape.serialization.get(
639 'timestampFormat', self.HEADER_TIMESTAMP_FORMAT
640 )
641 return self._convert_timestamp_to_str(timestamp, timestamp_format)
642 elif shape.type_name == 'list':
643 converted_value = [
644 self._convert_header_value(shape.member, v)
645 for v in value
646 if v is not None
647 ]
648 return ",".join(converted_value)
649 elif is_json_value_header(shape):
650 # Serialize with no spaces after separators to save space in
651 # the header.
652 return self._get_base64(json.dumps(value, separators=(',', ':')))
653 else:
654 return value
657class RestJSONSerializer(BaseRestSerializer, JSONSerializer):
658 def _serialize_empty_body(self):
659 return b'{}'
661 def _requires_empty_body(self, shape):
662 """
663 Serialize an empty JSON object whenever the shape has
664 members not targeting a location.
665 """
666 for member, val in shape.members.items():
667 if 'location' not in val.serialization:
668 return True
669 return False
671 def _serialize_content_type(self, serialized, shape, shape_members):
672 """Set Content-Type to application/json for all structured bodies."""
673 payload = shape.serialization.get('payload')
674 if self._has_streaming_payload(payload, shape_members):
675 # Don't apply content-type to streaming bodies
676 return
678 has_body = serialized['body'] != b''
679 has_content_type = has_header('Content-Type', serialized['headers'])
680 if has_body and not has_content_type:
681 serialized['headers']['Content-Type'] = 'application/json'
683 def _serialize_body_params(self, params, shape):
684 serialized_body = self.MAP_TYPE()
685 self._serialize(serialized_body, params, shape)
686 return json.dumps(serialized_body).encode(self.DEFAULT_ENCODING)
689class RestXMLSerializer(BaseRestSerializer):
690 TIMESTAMP_FORMAT = 'iso8601'
692 def _serialize_body_params(self, params, shape):
693 root_name = shape.serialization['name']
694 pseudo_root = ElementTree.Element('')
695 self._serialize(shape, params, pseudo_root, root_name)
696 real_root = list(pseudo_root)[0]
697 return ElementTree.tostring(real_root, encoding=self.DEFAULT_ENCODING)
699 def _serialize(self, shape, params, xmlnode, name):
700 method = getattr(
701 self,
702 '_serialize_type_%s' % shape.type_name,
703 self._default_serialize,
704 )
705 method(xmlnode, params, shape, name)
707 def _serialize_type_structure(self, xmlnode, params, shape, name):
708 structure_node = ElementTree.SubElement(xmlnode, name)
710 if 'xmlNamespace' in shape.serialization:
711 namespace_metadata = shape.serialization['xmlNamespace']
712 attribute_name = 'xmlns'
713 if namespace_metadata.get('prefix'):
714 attribute_name += ':%s' % namespace_metadata['prefix']
715 structure_node.attrib[attribute_name] = namespace_metadata['uri']
716 for key, value in params.items():
717 member_shape = shape.members[key]
718 member_name = member_shape.serialization.get('name', key)
719 # We need to special case member shapes that are marked as an
720 # xmlAttribute. Rather than serializing into an XML child node,
721 # we instead serialize the shape to an XML attribute of the
722 # *current* node.
723 if value is None:
724 # Don't serialize any param whose value is None.
725 return
726 if member_shape.serialization.get('xmlAttribute'):
727 # xmlAttributes must have a serialization name.
728 xml_attribute_name = member_shape.serialization['name']
729 structure_node.attrib[xml_attribute_name] = value
730 continue
731 self._serialize(member_shape, value, structure_node, member_name)
733 def _serialize_type_list(self, xmlnode, params, shape, name):
734 member_shape = shape.member
735 if shape.serialization.get('flattened'):
736 element_name = name
737 list_node = xmlnode
738 else:
739 element_name = member_shape.serialization.get('name', 'member')
740 list_node = ElementTree.SubElement(xmlnode, name)
741 for item in params:
742 self._serialize(member_shape, item, list_node, element_name)
744 def _serialize_type_map(self, xmlnode, params, shape, name):
745 # Given the ``name`` of MyMap, and input of {"key1": "val1"}
746 # we serialize this as:
747 # <MyMap>
748 # <entry>
749 # <key>key1</key>
750 # <value>val1</value>
751 # </entry>
752 # </MyMap>
753 node = ElementTree.SubElement(xmlnode, name)
754 # TODO: handle flattened maps.
755 for key, value in params.items():
756 entry_node = ElementTree.SubElement(node, 'entry')
757 key_name = self._get_serialized_name(shape.key, default_name='key')
758 val_name = self._get_serialized_name(
759 shape.value, default_name='value'
760 )
761 self._serialize(shape.key, key, entry_node, key_name)
762 self._serialize(shape.value, value, entry_node, val_name)
764 def _serialize_type_boolean(self, xmlnode, params, shape, name):
765 # For scalar types, the 'params' attr is actually just a scalar
766 # value representing the data we need to serialize as a boolean.
767 # It will either be 'true' or 'false'
768 node = ElementTree.SubElement(xmlnode, name)
769 if params:
770 str_value = 'true'
771 else:
772 str_value = 'false'
773 node.text = str_value
775 def _serialize_type_blob(self, xmlnode, params, shape, name):
776 node = ElementTree.SubElement(xmlnode, name)
777 node.text = self._get_base64(params)
779 def _serialize_type_timestamp(self, xmlnode, params, shape, name):
780 node = ElementTree.SubElement(xmlnode, name)
781 node.text = self._convert_timestamp_to_str(
782 params, shape.serialization.get('timestampFormat')
783 )
785 def _default_serialize(self, xmlnode, params, shape, name):
786 node = ElementTree.SubElement(xmlnode, name)
787 node.text = str(params)
790SERIALIZERS = {
791 'ec2': EC2Serializer,
792 'query': QuerySerializer,
793 'json': JSONSerializer,
794 'rest-json': RestJSONSerializer,
795 'rest-xml': RestXMLSerializer,
796}