Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
serialize.py811 linesDownload Raw Back to botocore
1# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License"). You4# may not use this file except in compliance with the License. A copy of5# the License is located at6#7# http://aws.amazon.com/apache2.0/8#9# or in the "license" file accompanying this file. This file is10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF11# ANY KIND, either express or implied. See the License for the specific12# language governing permissions and limitations under the License.13"""Protocol input serializes.14 15This module contains classes that implement input serialization16for the various AWS protocol types.17 18These classes essentially take user input, a model object that19represents what the expected input should look like, and it returns20a dictionary that contains the various parts of a request.  A few21high level design decisions:22 23 24* Each protocol type maps to a separate class, all inherit from25  ``Serializer``.26* The return value for ``serialize_to_request`` (the main entry27  point) returns a dictionary that represents a request.  This28  will have keys like ``url_path``, ``query_string``, etc.  This29  is done so that it's a) easy to test and b) not tied to a30  particular HTTP library.  See the ``serialize_to_request`` docstring31  for more details.32 33Unicode34-------35 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 base6441import calendar42import datetime43import json44import re45from xml.etree import ElementTree46 47from botocore import validate48from botocore.compat import formatdate49from botocore.exceptions import ParamValidationError50from botocore.utils import (51    has_header,52    is_json_value_header,53    parse_to_aware_datetime,54    percent_encode,55)56 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\.\-]+$")63 64 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 serializer72 73 74class Serializer:75    DEFAULT_METHOD = 'POST'76    # Clients can change this to a different MutableMapping77    # (i.e OrderedDict) if they want.  This is used in the78    # compliance test to match the hash ordering used in the79    # tests.80    MAP_TYPE = dict81    DEFAULT_ENCODING = 'utf-8'82 83    def serialize_to_request(self, parameters, operation_model):84        """Serialize parameters into an HTTP request.85 86        This method takes user provided parameters and a shape87        model and serializes the parameters to an HTTP request.88        More specifically, this method returns information about89        parts of the HTTP request, it does not enforce a particular90        interface or standard for an HTTP request.  It instead returns91        a dictionary of:92 93            * 'url_path'94            * 'host_prefix'95            * 'query_string'96            * 'headers'97            * 'body'98            * 'method'99 100        It is then up to consumers to decide how to map this to a Request101        object of their HTTP library of choice.  Below is an example102        return value::103 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': '/'}113 114        :param parameters: The dictionary input parameters for the115            operation (i.e the user input).116        :param operation_model: The OperationModel object that describes117            the operation.118        """119        raise NotImplementedError("serialize_to_request")120 121    def _create_default_request(self):122        # Creates a boilerplate default request dict that subclasses123        # 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 serialized133 134    # Some extra utility methods subclasses can use.135 136    def _timestamp_iso8601(self, value):137        if value.microsecond > 0:138            timestamp_format = ISO8601_MICRO139        else:140            timestamp_format = ISO8601141        return value.strftime(timestamp_format)142 143    def _timestamp_unixtimestamp(self, value):144        return int(calendar.timegm(value.timetuple()))145 146    def _timestamp_rfc822(self, value):147        if isinstance(value, datetime.datetime):148            value = self._timestamp_unixtimestamp(value)149        return formatdate(value, usegmt=True)150 151    def _convert_timestamp_to_str(self, value, timestamp_format=None):152        if timestamp_format is None:153            timestamp_format = self.TIMESTAMP_FORMAT154        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_value159 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)164 165    def _get_base64(self, value):166        # Returns the base64-encoded version of value, handling167        # both strings and bytes. The returned value is a string168        # 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)172 173    def _expand_host_prefix(self, parameters, operation_model):174        operation_endpoint = operation_model.endpoint175        if (176            operation_endpoint is None177            or 'hostPrefix' not in operation_endpoint178        ):179            return None180 181        host_prefix_expression = operation_endpoint['hostPrefix']182        input_members = operation_model.input_shape.members183        host_labels = [184            member185            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] = param195        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)204 205 206class QuerySerializer(Serializer):207    TIMESTAMP_FORMAT = 'iso8601'208 209    def serialize_to_request(self, parameters, operation_model):210        shape = operation_model.input_shape211        serialized = self._create_default_request()212        serialized['method'] = operation_model.http.get(213            'method', self.DEFAULT_METHOD214        )215        serialized['headers'] = {216            'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'217        }218        # The query serializer only deals with body params so219        # that's what we hand off the _serialize_* methods.220        body_params = self.MAP_TYPE()221        body_params['Action'] = operation_model.name222        body_params['Version'] = operation_model.metadata['apiVersion']223        if shape is not None:224            self._serialize(body_params, parameters, shape)225        serialized['body'] = body_params226 227        host_prefix = self._expand_host_prefix(parameters, operation_model)228        if host_prefix is not None:229            serialized['host_prefix'] = host_prefix230 231        return serialized232 233    def _serialize(self, serialized, value, shape, prefix=''):234        # serialized: The dict that is incrementally added to with the235        #             final serialized parameters.236        # value: The current user input value.237        # shape: The shape object that describes the structure of the238        #        input.239        # prefix: The incrementally built up prefix for the serialized240        #         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)247 248    def _serialize_type_structure(self, serialized, value, shape, prefix=''):249        members = shape.members250        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)256 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            return262        if self._is_shape_flattened(shape):263            list_prefix = prefix264            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.member274            self._serialize(serialized, element, element_shape, element_prefix)275 276    def _serialize_type_map(self, serialized, value, shape, prefix=''):277        if self._is_shape_flattened(shape):278            full_prefix = prefix279        else:280            full_prefix = '%s.entry' % prefix281        template = full_prefix + '.{i}.{suffix}'282        key_shape = shape.key283        value_shape = shape.value284        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)291 292    def _serialize_type_blob(self, serialized, value, shape, prefix=''):293        # Blob args must be base64 encoded.294        serialized[prefix] = self._get_base64(value)295 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        )300 301    def _serialize_type_boolean(self, serialized, value, shape, prefix=''):302        if value:303            serialized[prefix] = 'true'304        else:305            serialized[prefix] = 'false'306 307    def _default_serialize(self, serialized, value, shape, prefix=''):308        serialized[prefix] = value309 310    def _is_shape_flattened(self, shape):311        return shape.serialization.get('flattened')312 313 314class EC2Serializer(QuerySerializer):315    """EC2 specific customizations to the query protocol serializers.316 317    The EC2 model is almost, but not exactly, similar to the query protocol318    serializer.  This class encapsulates those differences.  The model319    will have be marked with a ``protocol`` of ``ec2``, so you don't need320    to worry about wiring this class up correctly.321 322    """323 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 capitalized331            # on input for the ec2 protocol.332            name = shape.serialization['name']333            return name[0].upper() + name[1:]334        else:335            return default_name336 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.member341            self._serialize(serialized, element, element_shape, element_prefix)342 343 344class JSONSerializer(Serializer):345    TIMESTAMP_FORMAT = 'unixtimestamp'346 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_METHOD356        )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_shape363        if input_shape is not None:364            self._serialize(body, parameters, input_shape)365        serialized['body'] = json.dumps(body).encode(self.DEFAULT_ENCODING)366 367        host_prefix = self._expand_host_prefix(parameters, operation_model)368        if host_prefix is not None:369            serialized['host_prefix'] = host_prefix370 371        return serialized372 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)380 381    def _serialize_type_structure(self, serialized, value, shape, key):382        if shape.is_document_type:383            serialized[key] = value384        else:385            if key is not None:386                # If a key is provided, this is a result of a recursive387                # call so we need to add a new child dict as the value388                # of the passed in serialized dict.  We'll then add389                # all the structure members as key/vals in the new serialized390                # dictionary we just created.391                new_serialized = self.MAP_TYPE()392                serialized[key] = new_serialized393                serialized = new_serialized394            members = shape.members395            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_key401                )402 403    def _serialize_type_map(self, serialized, value, shape, key):404        map_obj = self.MAP_TYPE()405        serialized[key] = map_obj406        for sub_key, sub_value in value.items():407            self._serialize(map_obj, sub_value, shape.value, sub_key)408 409    def _serialize_type_list(self, serialized, value, shape, key):410        list_obj = []411        serialized[key] = list_obj412        for list_item in value:413            wrapper = {}414            # The JSON list serialization is the only case where we aren't415            # setting a key on a dict.  We handle this by using416            # a __current__ key on a wrapper dict to serialize each417            # list item before appending it to the serialized list.418            self._serialize(wrapper, list_item, shape.member, "__current__")419            list_obj.append(wrapper["__current__"])420 421    def _default_serialize(self, serialized, value, shape, key):422        serialized[key] = value423 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        )428 429    def _serialize_type_blob(self, serialized, value, shape, key):430        serialized[key] = self._get_base64(value)431 432 433class BaseRestSerializer(Serializer):434    """Base class for rest protocols.435 436    The only variance between the various rest protocols is the437    way that the body is serialized.  All other aspects (headers, uri, etc.)438    are the same and logic for serializing those aspects lives here.439 440    Subclasses must implement the ``_serialize_body_params`` method.441 442    """443 444    QUERY_STRING_TIMESTAMP_FORMAT = 'iso8601'445    HEADER_TIMESTAMP_FORMAT = 'rfc822'446    # This is a list of known values for the "location" key in the447    # serialization dict.  The location key tells us where on the request448    # to put the serialized value.449    KNOWN_LOCATIONS = ['uri', 'querystring', 'header', 'headers']450 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_METHOD455        )456        shape = operation_model.input_shape457        if shape is None:458            serialized['url_path'] = operation_model.http['requestUri']459            return serialized460        shape_members = shape.members461        # While the ``serialized`` key holds the final serialized request462        # data, we need interim dicts for the various locations of the463        # request.  We need this for the uri_path_kwargs and the464        # query_string_kwargs because they are templated, so we need465        # to gather all the needed data for the string template,466        # then we render the template.  The body_kwargs is needed467        # because once we've collected them all, we run them through468        # _serialize_body_params, which for rest-json, creates JSON,469        # and for rest-xml, will create XML.  This is what the470        # ``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                continue481            self._partition_parameters(482                partitioned, param_name, param_value, shape_members483            )484        serialized['url_path'] = self._render_uri_template(485            operation_model.http['requestUri'], partitioned['uri_path_kwargs']486        )487 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 case494        # 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_members501        )502        self._serialize_content_type(serialized, shape, shape_members)503 504        host_prefix = self._expand_host_prefix(parameters, operation_model)505        if host_prefix is not None:506            serialized['host_prefix'] = host_prefix507 508        return serialized509 510    def _render_uri_template(self, uri_template, params):511        # We need to handle two cases::512        #513        # /{Bucket}/foo514        # /{Key+}/bar515        # A label ending with '+' is greedy.  There can only516        # 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)528 529    def _serialize_payload(530        self, partitioned, parameters, serialized, shape, shape_members531    ):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 shape536        # shape_members - The members of the input struct shape537        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 the540            # value of the payload.541            body_payload = parameters.get(payload_member, b'')542            body_payload = self._encode_payload(body_payload)543            serialized['body'] = body_payload544        elif payload_member is not None:545            # If there's a payload member, we serialized that546            # 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'], shape557            )558        elif self._requires_empty_body(shape):559            serialized['body'] = self._serialize_empty_body()560 561    def _serialize_empty_body(self):562        return b''563 564    def _serialize_content_type(self, serialized, shape, shape_members):565        """566        Some protocols require varied Content-Type headers567        depending on user input. This allows subclasses to apply568        this conditionally.569        """570        pass571 572    def _requires_empty_body(self, shape):573        """574        Some protocols require a specific body to represent an empty575        payload. This allows subclasses to apply this conditionally.576        """577        return False578 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        )585 586    def _encode_payload(self, body):587        if isinstance(body, str):588            return body.encode(self.DEFAULT_ENCODING)589        return body590 591    def _partition_parameters(592        self, partitioned, param_name, param_value, shape_members593    ):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, some597        # 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_value603        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_str609            elif member.type_name == 'timestamp':610                timestamp_format = member.serialization.get(611                    'timestampFormat', self.QUERY_STRING_TIMESTAMP_FORMAT612                )613                timestamp = self._convert_timestamp_to_str(614                    param_value, timestamp_format615                )616                partitioned['query_string_kwargs'][key_name] = timestamp617            else:618                partitioned['query_string_kwargs'][key_name] = param_value619        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 headers623                return624            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_name630            # The value provided by the user is a dict so we'll be631            # creating multiple header key/val pairs.  The key632            # 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_value636            )637        else:638            partitioned['body_kwargs'][param_name] = param_value639 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 + key643            headers[full_key] = val644 645    def _serialize_body_params(self, params, shape):646        raise NotImplementedError('_serialize_body_params')647 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_FORMAT654            )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 value660                if v is not None661            ]662            return ",".join(converted_value)663        elif is_json_value_header(shape):664            # Serialize with no spaces after separators to save space in665            # the header.666            return self._get_base64(json.dumps(value, separators=(',', ':')))667        else:668            return value669 670 671class RestJSONSerializer(BaseRestSerializer, JSONSerializer):672    def _serialize_empty_body(self):673        return b'{}'674 675    def _requires_empty_body(self, shape):676        """677        Serialize an empty JSON object whenever the shape has678        members not targeting a location.679        """680        for member, val in shape.members.items():681            if 'location' not in val.serialization:682                return True683        return False684 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 bodies690            return691 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'696 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)701 702 703class RestXMLSerializer(BaseRestSerializer):704    TIMESTAMP_FORMAT = 'iso8601'705 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)712 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)720 721    def _serialize_type_structure(self, xmlnode, params, shape, name):722        structure_node = ElementTree.SubElement(xmlnode, name)723 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 an734            # xmlAttribute.  Rather than serializing into an XML child node,735            # we instead serialize the shape to an XML attribute of the736            # *current* node.737            if value is None:738                # Don't serialize any param whose value is None.739                return740            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] = value744                continue745            self._serialize(member_shape, value, structure_node, member_name)746 747    def _serialize_type_list(self, xmlnode, params, shape, name):748        member_shape = shape.member749        if shape.serialization.get('flattened'):750            element_name = name751            list_node = xmlnode752        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)757 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)777 778    def _serialize_type_boolean(self, xmlnode, params, shape, name):779        # For scalar types, the 'params' attr is actually just a scalar780        # 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_value788 789    def _serialize_type_blob(self, xmlnode, params, shape, name):790        node = ElementTree.SubElement(xmlnode, name)791        node.text = self._get_base64(params)792 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        )798 799    def _default_serialize(self, xmlnode, params, shape, name):800        node = ElementTree.SubElement(xmlnode, name)801        node.text = str(params)802 803 804SERIALIZERS = {805    'ec2': EC2Serializer,806    'query': QuerySerializer,807    'json': JSONSerializer,808    'rest-json': RestJSONSerializer,809    'rest-xml': RestXMLSerializer,810}811