Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
model.py955 linesDownload Raw Back to botocore
1# Copyright 2015 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"""Abstractions to interact with service models."""14from collections import defaultdict15from typing import NamedTuple, Union16 17from botocore.compat import OrderedDict18from botocore.exceptions import (19    MissingServiceIdError,20    UndefinedModelAttributeError,21)22from botocore.utils import CachedProperty, hyphenize_service_id, instance_cache23 24NOT_SET = object()25 26 27class NoShapeFoundError(Exception):28    pass29 30 31class InvalidShapeError(Exception):32    pass33 34 35class OperationNotFoundError(Exception):36    pass37 38 39class InvalidShapeReferenceError(Exception):40    pass41 42 43class ServiceId(str):44    def hyphenize(self):45        return hyphenize_service_id(self)46 47 48class Shape:49    """Object representing a shape from the service model."""50 51    # To simplify serialization logic, all shape params that are52    # related to serialization are moved from the top level hash into53    # a 'serialization' hash.  This list below contains the names of all54    # the attributes that should be moved.55    SERIALIZED_ATTRS = [56        'locationName',57        'queryName',58        'flattened',59        'location',60        'payload',61        'streaming',62        'timestampFormat',63        'xmlNamespace',64        'resultWrapper',65        'xmlAttribute',66        'eventstream',67        'event',68        'eventheader',69        'eventpayload',70        'jsonvalue',71        'timestampFormat',72        'hostLabel',73    ]74    METADATA_ATTRS = [75        'required',76        'min',77        'max',78        'pattern',79        'sensitive',80        'enum',81        'idempotencyToken',82        'error',83        'exception',84        'endpointdiscoveryid',85        'retryable',86        'document',87        'union',88        'contextParam',89        'clientContextParams',90        'requiresLength',91    ]92    MAP_TYPE = OrderedDict93 94    def __init__(self, shape_name, shape_model, shape_resolver=None):95        """96 97        :type shape_name: string98        :param shape_name: The name of the shape.99 100        :type shape_model: dict101        :param shape_model: The shape model.  This would be the value102            associated with the key in the "shapes" dict of the103            service model (i.e ``model['shapes'][shape_name]``)104 105        :type shape_resolver: botocore.model.ShapeResolver106        :param shape_resolver: A shape resolver object.  This is used to107            resolve references to other shapes.  For scalar shape types108            (string, integer, boolean, etc.), this argument is not109            required.  If a shape_resolver is not provided for a complex110            type, then a ``ValueError`` will be raised when an attempt111            to resolve a shape is made.112 113        """114        self.name = shape_name115        self.type_name = shape_model['type']116        self.documentation = shape_model.get('documentation', '')117        self._shape_model = shape_model118        if shape_resolver is None:119            # If a shape_resolver is not provided, we create an object120            # that will throw errors if you attempt to resolve121            # a shape.  This is actually ok for scalar shapes122            # because they don't need to resolve shapes and shouldn't123            # be required to provide an object they won't use.124            shape_resolver = UnresolvableShapeMap()125        self._shape_resolver = shape_resolver126        self._cache = {}127 128    @CachedProperty129    def serialization(self):130        """Serialization information about the shape.131 132        This contains information that may be needed for input serialization133        or response parsing.  This can include:134 135            * name136            * queryName137            * flattened138            * location139            * payload140            * streaming141            * xmlNamespace142            * resultWrapper143            * xmlAttribute144            * jsonvalue145            * timestampFormat146 147        :rtype: dict148        :return: Serialization information about the shape.149 150        """151        model = self._shape_model152        serialization = {}153        for attr in self.SERIALIZED_ATTRS:154            if attr in self._shape_model:155                serialization[attr] = model[attr]156        # For consistency, locationName is renamed to just 'name'.157        if 'locationName' in serialization:158            serialization['name'] = serialization.pop('locationName')159        return serialization160 161    @CachedProperty162    def metadata(self):163        """Metadata about the shape.164 165        This requires optional information about the shape, including:166 167            * min168            * max169            * pattern170            * enum171            * sensitive172            * required173            * idempotencyToken174            * document175            * union176            * contextParam177            * clientContextParams178            * requiresLength179 180        :rtype: dict181        :return: Metadata about the shape.182 183        """184        model = self._shape_model185        metadata = {}186        for attr in self.METADATA_ATTRS:187            if attr in self._shape_model:188                metadata[attr] = model[attr]189        return metadata190 191    @CachedProperty192    def required_members(self):193        """A list of members that are required.194 195        A structure shape can define members that are required.196        This value will return a list of required members.  If there197        are no required members an empty list is returned.198 199        """200        return self.metadata.get('required', [])201 202    def _resolve_shape_ref(self, shape_ref):203        return self._shape_resolver.resolve_shape_ref(shape_ref)204 205    def __repr__(self):206        return f"<{self.__class__.__name__}({self.name})>"207 208    @property209    def event_stream_name(self):210        return None211 212 213class StructureShape(Shape):214    @CachedProperty215    def members(self):216        members = self._shape_model.get('members', self.MAP_TYPE())217        # The members dict looks like:218        #    'members': {219        #        'MemberName': {'shape': 'shapeName'},220        #        'MemberName2': {'shape': 'shapeName'},221        #    }222        # We return a dict of member name to Shape object.223        shape_members = self.MAP_TYPE()224        for name, shape_ref in members.items():225            shape_members[name] = self._resolve_shape_ref(shape_ref)226        return shape_members227 228    @CachedProperty229    def event_stream_name(self):230        for member_name, member in self.members.items():231            if member.serialization.get('eventstream'):232                return member_name233        return None234 235    @CachedProperty236    def error_code(self):237        if not self.metadata.get('exception', False):238            return None239        error_metadata = self.metadata.get("error", {})240        code = error_metadata.get("code")241        if code:242            return code243        # Use the exception name if there is no explicit code modeled244        return self.name245 246    @CachedProperty247    def is_document_type(self):248        return self.metadata.get('document', False)249 250    @CachedProperty251    def is_tagged_union(self):252        return self.metadata.get('union', False)253 254 255class ListShape(Shape):256    @CachedProperty257    def member(self):258        return self._resolve_shape_ref(self._shape_model['member'])259 260 261class MapShape(Shape):262    @CachedProperty263    def key(self):264        return self._resolve_shape_ref(self._shape_model['key'])265 266    @CachedProperty267    def value(self):268        return self._resolve_shape_ref(self._shape_model['value'])269 270 271class StringShape(Shape):272    @CachedProperty273    def enum(self):274        return self.metadata.get('enum', [])275 276 277class StaticContextParameter(NamedTuple):278    name: str279    value: Union[bool, str]280 281 282class ContextParameter(NamedTuple):283    name: str284    member_name: str285 286 287class ClientContextParameter(NamedTuple):288    name: str289    type: str290    documentation: str291 292 293class ServiceModel:294    """295 296    :ivar service_description: The parsed service description dictionary.297 298    """299 300    def __init__(self, service_description, service_name=None):301        """302 303        :type service_description: dict304        :param service_description: The service description model.  This value305            is obtained from a botocore.loader.Loader, or from directly loading306            the file yourself::307 308                service_description = json.load(309                    open('/path/to/service-description-model.json'))310                model = ServiceModel(service_description)311 312        :type service_name: str313        :param service_name: The name of the service.  Normally this is314            the endpoint prefix defined in the service_description.  However,315            you can override this value to provide a more convenient name.316            This is done in a few places in botocore (ses instead of email,317            emr instead of elasticmapreduce).  If this value is not provided,318            it will default to the endpointPrefix defined in the model.319 320        """321        self._service_description = service_description322        # We want clients to be able to access metadata directly.323        self.metadata = service_description.get('metadata', {})324        self._shape_resolver = ShapeResolver(325            service_description.get('shapes', {})326        )327        self._signature_version = NOT_SET328        self._service_name = service_name329        self._instance_cache = {}330 331    def shape_for(self, shape_name, member_traits=None):332        return self._shape_resolver.get_shape_by_name(333            shape_name, member_traits334        )335 336    def shape_for_error_code(self, error_code):337        return self._error_code_cache.get(error_code, None)338 339    @CachedProperty340    def _error_code_cache(self):341        error_code_cache = {}342        for error_shape in self.error_shapes:343            code = error_shape.error_code344            error_code_cache[code] = error_shape345        return error_code_cache346 347    def resolve_shape_ref(self, shape_ref):348        return self._shape_resolver.resolve_shape_ref(shape_ref)349 350    @CachedProperty351    def shape_names(self):352        return list(self._service_description.get('shapes', {}))353 354    @CachedProperty355    def error_shapes(self):356        error_shapes = []357        for shape_name in self.shape_names:358            error_shape = self.shape_for(shape_name)359            if error_shape.metadata.get('exception', False):360                error_shapes.append(error_shape)361        return error_shapes362 363    @instance_cache364    def operation_model(self, operation_name):365        try:366            model = self._service_description['operations'][operation_name]367        except KeyError:368            raise OperationNotFoundError(operation_name)369        return OperationModel(model, self, operation_name)370 371    @CachedProperty372    def documentation(self):373        return self._service_description.get('documentation', '')374 375    @CachedProperty376    def operation_names(self):377        return list(self._service_description.get('operations', []))378 379    @CachedProperty380    def service_name(self):381        """The name of the service.382 383        This defaults to the endpointPrefix defined in the service model.384        However, this value can be overriden when a ``ServiceModel`` is385        created.  If a service_name was not provided when the ``ServiceModel``386        was created and if there is no endpointPrefix defined in the387        service model, then an ``UndefinedModelAttributeError`` exception388        will be raised.389 390        """391        if self._service_name is not None:392            return self._service_name393        else:394            return self.endpoint_prefix395 396    @CachedProperty397    def service_id(self):398        try:399            return ServiceId(self._get_metadata_property('serviceId'))400        except UndefinedModelAttributeError:401            raise MissingServiceIdError(service_name=self._service_name)402 403    @CachedProperty404    def signing_name(self):405        """The name to use when computing signatures.406 407        If the model does not define a signing name, this408        value will be the endpoint prefix defined in the model.409        """410        signing_name = self.metadata.get('signingName')411        if signing_name is None:412            signing_name = self.endpoint_prefix413        return signing_name414 415    @CachedProperty416    def api_version(self):417        return self._get_metadata_property('apiVersion')418 419    @CachedProperty420    def protocol(self):421        return self._get_metadata_property('protocol')422 423    @CachedProperty424    def endpoint_prefix(self):425        return self._get_metadata_property('endpointPrefix')426 427    @CachedProperty428    def endpoint_discovery_operation(self):429        for operation in self.operation_names:430            model = self.operation_model(operation)431            if model.is_endpoint_discovery_operation:432                return model433 434    @CachedProperty435    def endpoint_discovery_required(self):436        for operation in self.operation_names:437            model = self.operation_model(operation)438            if (439                model.endpoint_discovery is not None440                and model.endpoint_discovery.get('required')441            ):442                return True443        return False444 445    @CachedProperty446    def client_context_parameters(self):447        params = self._service_description.get('clientContextParams', {})448        return [449            ClientContextParameter(450                name=param_name,451                type=param_val['type'],452                documentation=param_val['documentation'],453            )454            for param_name, param_val in params.items()455        ]456 457    def _get_metadata_property(self, name):458        try:459            return self.metadata[name]460        except KeyError:461            raise UndefinedModelAttributeError(462                f'"{name}" not defined in the metadata of the model: {self}'463            )464 465    # Signature version is one of the rare properties466    # that can be modified so a CachedProperty is not used here.467 468    @property469    def signature_version(self):470        if self._signature_version is NOT_SET:471            signature_version = self.metadata.get('signatureVersion')472            self._signature_version = signature_version473        return self._signature_version474 475    @signature_version.setter476    def signature_version(self, value):477        self._signature_version = value478 479    def __repr__(self):480        return f'{self.__class__.__name__}({self.service_name})'481 482 483class OperationModel:484    def __init__(self, operation_model, service_model, name=None):485        """486 487        :type operation_model: dict488        :param operation_model: The operation model.  This comes from the489            service model, and is the value associated with the operation490            name in the service model (i.e ``model['operations'][op_name]``).491 492        :type service_model: botocore.model.ServiceModel493        :param service_model: The service model associated with the operation.494 495        :type name: string496        :param name: The operation name.  This is the operation name exposed to497            the users of this model.  This can potentially be different from498            the "wire_name", which is the operation name that *must* by499            provided over the wire.  For example, given::500 501               "CreateCloudFrontOriginAccessIdentity":{502                 "name":"CreateCloudFrontOriginAccessIdentity2014_11_06",503                  ...504              }505 506           The ``name`` would be ``CreateCloudFrontOriginAccessIdentity``,507           but the ``self.wire_name`` would be508           ``CreateCloudFrontOriginAccessIdentity2014_11_06``, which is the509           value we must send in the corresponding HTTP request.510 511        """512        self._operation_model = operation_model513        self._service_model = service_model514        self._api_name = name515        # Clients can access '.name' to get the operation name516        # and '.metadata' to get the top level metdata of the service.517        self._wire_name = operation_model.get('name')518        self.metadata = service_model.metadata519        self.http = operation_model.get('http', {})520 521    @CachedProperty522    def name(self):523        if self._api_name is not None:524            return self._api_name525        else:526            return self.wire_name527 528    @property529    def wire_name(self):530        """The wire name of the operation.531 532        In many situations this is the same value as the533        ``name``, value, but in some services, the operation name534        exposed to the user is different from the operation name535        we send across the wire (e.g cloudfront).536 537        Any serialization code should use ``wire_name``.538 539        """540        return self._operation_model.get('name')541 542    @property543    def service_model(self):544        return self._service_model545 546    @CachedProperty547    def documentation(self):548        return self._operation_model.get('documentation', '')549 550    @CachedProperty551    def deprecated(self):552        return self._operation_model.get('deprecated', False)553 554    @CachedProperty555    def endpoint_discovery(self):556        # Explicit None default. An empty dictionary for this trait means it is557        # enabled but not required to be used.558        return self._operation_model.get('endpointdiscovery', None)559 560    @CachedProperty561    def is_endpoint_discovery_operation(self):562        return self._operation_model.get('endpointoperation', False)563 564    @CachedProperty565    def input_shape(self):566        if 'input' not in self._operation_model:567            # Some operations do not accept any input and do not define an568            # input shape.569            return None570        return self._service_model.resolve_shape_ref(571            self._operation_model['input']572        )573 574    @CachedProperty575    def output_shape(self):576        if 'output' not in self._operation_model:577            # Some operations do not define an output shape,578            # in which case we return None to indicate the579            # operation has no expected output.580            return None581        return self._service_model.resolve_shape_ref(582            self._operation_model['output']583        )584 585    @CachedProperty586    def idempotent_members(self):587        input_shape = self.input_shape588        if not input_shape:589            return []590 591        return [592            name593            for (name, shape) in input_shape.members.items()594            if 'idempotencyToken' in shape.metadata595            and shape.metadata['idempotencyToken']596        ]597 598    @CachedProperty599    def static_context_parameters(self):600        params = self._operation_model.get('staticContextParams', {})601        return [602            StaticContextParameter(name=name, value=props.get('value'))603            for name, props in params.items()604        ]605 606    @CachedProperty607    def context_parameters(self):608        if not self.input_shape:609            return []610 611        return [612            ContextParameter(613                name=shape.metadata['contextParam']['name'],614                member_name=name,615            )616            for name, shape in self.input_shape.members.items()617            if 'contextParam' in shape.metadata618            and 'name' in shape.metadata['contextParam']619        ]620 621    @CachedProperty622    def request_compression(self):623        return self._operation_model.get('requestcompression')624 625    @CachedProperty626    def auth_type(self):627        return self._operation_model.get('authtype')628 629    @CachedProperty630    def error_shapes(self):631        shapes = self._operation_model.get("errors", [])632        return list(self._service_model.resolve_shape_ref(s) for s in shapes)633 634    @CachedProperty635    def endpoint(self):636        return self._operation_model.get('endpoint')637 638    @CachedProperty639    def http_checksum_required(self):640        return self._operation_model.get('httpChecksumRequired', False)641 642    @CachedProperty643    def http_checksum(self):644        return self._operation_model.get('httpChecksum', {})645 646    @CachedProperty647    def has_event_stream_input(self):648        return self.get_event_stream_input() is not None649 650    @CachedProperty651    def has_event_stream_output(self):652        return self.get_event_stream_output() is not None653 654    def get_event_stream_input(self):655        return self._get_event_stream(self.input_shape)656 657    def get_event_stream_output(self):658        return self._get_event_stream(self.output_shape)659 660    def _get_event_stream(self, shape):661        """Returns the event stream member's shape if any or None otherwise."""662        if shape is None:663            return None664        event_name = shape.event_stream_name665        if event_name:666            return shape.members[event_name]667        return None668 669    @CachedProperty670    def has_streaming_input(self):671        return self.get_streaming_input() is not None672 673    @CachedProperty674    def has_streaming_output(self):675        return self.get_streaming_output() is not None676 677    def get_streaming_input(self):678        return self._get_streaming_body(self.input_shape)679 680    def get_streaming_output(self):681        return self._get_streaming_body(self.output_shape)682 683    def _get_streaming_body(self, shape):684        """Returns the streaming member's shape if any; or None otherwise."""685        if shape is None:686            return None687        payload = shape.serialization.get('payload')688        if payload is not None:689            payload_shape = shape.members[payload]690            if payload_shape.type_name == 'blob':691                return payload_shape692        return None693 694    def __repr__(self):695        return f'{self.__class__.__name__}(name={self.name})'696 697 698class ShapeResolver:699    """Resolves shape references."""700 701    # Any type not in this mapping will default to the Shape class.702    SHAPE_CLASSES = {703        'structure': StructureShape,704        'list': ListShape,705        'map': MapShape,706        'string': StringShape,707    }708 709    def __init__(self, shape_map):710        self._shape_map = shape_map711        self._shape_cache = {}712 713    def get_shape_by_name(self, shape_name, member_traits=None):714        try:715            shape_model = self._shape_map[shape_name]716        except KeyError:717            raise NoShapeFoundError(shape_name)718        try:719            shape_cls = self.SHAPE_CLASSES.get(shape_model['type'], Shape)720        except KeyError:721            raise InvalidShapeError(722                f"Shape is missing required key 'type': {shape_model}"723            )724        if member_traits:725            shape_model = shape_model.copy()726            shape_model.update(member_traits)727        result = shape_cls(shape_name, shape_model, self)728        return result729 730    def resolve_shape_ref(self, shape_ref):731        # A shape_ref is a dict that has a 'shape' key that732        # refers to a shape name as well as any additional733        # member traits that are then merged over the shape734        # definition.  For example:735        # {"shape": "StringType", "locationName": "Foobar"}736        if len(shape_ref) == 1 and 'shape' in shape_ref:737            # It's just a shape ref with no member traits, we can avoid738            # a .copy().  This is the common case so it's specifically739            # called out here.740            return self.get_shape_by_name(shape_ref['shape'])741        else:742            member_traits = shape_ref.copy()743            try:744                shape_name = member_traits.pop('shape')745            except KeyError:746                raise InvalidShapeReferenceError(747                    f"Invalid model, missing shape reference: {shape_ref}"748                )749            return self.get_shape_by_name(shape_name, member_traits)750 751 752class UnresolvableShapeMap:753    """A ShapeResolver that will throw ValueErrors when shapes are resolved."""754 755    def get_shape_by_name(self, shape_name, member_traits=None):756        raise ValueError(757            f"Attempted to lookup shape '{shape_name}', but no shape map was provided."758        )759 760    def resolve_shape_ref(self, shape_ref):761        raise ValueError(762            f"Attempted to resolve shape '{shape_ref}', but no shape "763            f"map was provided."764        )765 766 767class DenormalizedStructureBuilder:768    """Build a StructureShape from a denormalized model.769 770    This is a convenience builder class that makes it easy to construct771    ``StructureShape``s based on a denormalized model.772 773    It will handle the details of creating unique shape names and creating774    the appropriate shape map needed by the ``StructureShape`` class.775 776    Example usage::777 778        builder = DenormalizedStructureBuilder()779        shape = builder.with_members({780            'A': {781                'type': 'structure',782                'members': {783                    'B': {784                        'type': 'structure',785                        'members': {786                            'C': {787                                'type': 'string',788                            }789                        }790                    }791                }792            }793        }).build_model()794        # ``shape`` is now an instance of botocore.model.StructureShape795 796    :type dict_type: class797    :param dict_type: The dictionary type to use, allowing you to opt-in798                      to using OrderedDict or another dict type. This can799                      be particularly useful for testing when order800                      matters, such as for documentation.801 802    """803 804    SCALAR_TYPES = (805        'string',806        'integer',807        'boolean',808        'blob',809        'float',810        'timestamp',811        'long',812        'double',813        'char',814    )815 816    def __init__(self, name=None):817        self.members = OrderedDict()818        self._name_generator = ShapeNameGenerator()819        if name is None:820            self.name = self._name_generator.new_shape_name('structure')821 822    def with_members(self, members):823        """824 825        :type members: dict826        :param members: The denormalized members.827 828        :return: self829 830        """831        self._members = members832        return self833 834    def build_model(self):835        """Build the model based on the provided members.836 837        :rtype: botocore.model.StructureShape838        :return: The built StructureShape object.839 840        """841        shapes = OrderedDict()842        denormalized = {843            'type': 'structure',844            'members': self._members,845        }846        self._build_model(denormalized, shapes, self.name)847        resolver = ShapeResolver(shape_map=shapes)848        return StructureShape(849            shape_name=self.name,850            shape_model=shapes[self.name],851            shape_resolver=resolver,852        )853 854    def _build_model(self, model, shapes, shape_name):855        if model['type'] == 'structure':856            shapes[shape_name] = self._build_structure(model, shapes)857        elif model['type'] == 'list':858            shapes[shape_name] = self._build_list(model, shapes)859        elif model['type'] == 'map':860            shapes[shape_name] = self._build_map(model, shapes)861        elif model['type'] in self.SCALAR_TYPES:862            shapes[shape_name] = self._build_scalar(model)863        else:864            raise InvalidShapeError(f"Unknown shape type: {model['type']}")865 866    def _build_structure(self, model, shapes):867        members = OrderedDict()868        shape = self._build_initial_shape(model)869        shape['members'] = members870 871        for name, member_model in model.get('members', OrderedDict()).items():872            member_shape_name = self._get_shape_name(member_model)873            members[name] = {'shape': member_shape_name}874            self._build_model(member_model, shapes, member_shape_name)875        return shape876 877    def _build_list(self, model, shapes):878        member_shape_name = self._get_shape_name(model)879        shape = self._build_initial_shape(model)880        shape['member'] = {'shape': member_shape_name}881        self._build_model(model['member'], shapes, member_shape_name)882        return shape883 884    def _build_map(self, model, shapes):885        key_shape_name = self._get_shape_name(model['key'])886        value_shape_name = self._get_shape_name(model['value'])887        shape = self._build_initial_shape(model)888        shape['key'] = {'shape': key_shape_name}889        shape['value'] = {'shape': value_shape_name}890        self._build_model(model['key'], shapes, key_shape_name)891        self._build_model(model['value'], shapes, value_shape_name)892        return shape893 894    def _build_initial_shape(self, model):895        shape = {896            'type': model['type'],897        }898        if 'documentation' in model:899            shape['documentation'] = model['documentation']900        for attr in Shape.METADATA_ATTRS:901            if attr in model:902                shape[attr] = model[attr]903        return shape904 905    def _build_scalar(self, model):906        return self._build_initial_shape(model)907 908    def _get_shape_name(self, model):909        if 'shape_name' in model:910            return model['shape_name']911        else:912            return self._name_generator.new_shape_name(model['type'])913 914 915class ShapeNameGenerator:916    """Generate unique shape names for a type.917 918    This class can be used in conjunction with the DenormalizedStructureBuilder919    to generate unique shape names for a given type.920 921    """922 923    def __init__(self):924        self._name_cache = defaultdict(int)925 926    def new_shape_name(self, type_name):927        """Generate a unique shape name.928 929        This method will guarantee a unique shape name each time it is930        called with the same type.931 932        ::933 934            >>> s = ShapeNameGenerator()935            >>> s.new_shape_name('structure')936            'StructureType1'937            >>> s.new_shape_name('structure')938            'StructureType2'939            >>> s.new_shape_name('list')940            'ListType1'941            >>> s.new_shape_name('list')942            'ListType2'943 944 945        :type type_name: string946        :param type_name: The type name (structure, list, map, string, etc.)947 948        :rtype: string949        :return: A unique shape name for the given type950 951        """952        self._name_cache[type_name] += 1953        current_index = self._name_cache[type_name]954        return f'{type_name.capitalize()}Type{current_index}'955 
codekingpro/portable-devtools · Team Ai