| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """Abstractions to interact with service models.""" |
|
|
| from collections import defaultdict |
| from typing import NamedTuple, Union |
|
|
| from botocore.auth import resolve_auth_type |
| from botocore.compat import OrderedDict |
| from botocore.exceptions import ( |
| MissingServiceIdError, |
| UndefinedModelAttributeError, |
| UnsupportedServiceProtocolsError, |
| ) |
| from botocore.utils import ( |
| PRIORITY_ORDERED_SUPPORTED_PROTOCOLS, |
| CachedProperty, |
| hyphenize_service_id, |
| instance_cache, |
| ) |
|
|
| NOT_SET = object() |
|
|
|
|
| class NoShapeFoundError(Exception): |
| pass |
|
|
|
|
| class InvalidShapeError(Exception): |
| pass |
|
|
|
|
| class OperationNotFoundError(Exception): |
| pass |
|
|
|
|
| class InvalidShapeReferenceError(Exception): |
| pass |
|
|
|
|
| class ServiceId(str): |
| def hyphenize(self): |
| return hyphenize_service_id(self) |
|
|
|
|
| class Shape: |
| """Object representing a shape from the service model.""" |
|
|
| |
| |
| |
| |
| SERIALIZED_ATTRS = [ |
| 'locationName', |
| 'queryName', |
| 'flattened', |
| 'location', |
| 'payload', |
| 'streaming', |
| 'timestampFormat', |
| 'xmlNamespace', |
| 'resultWrapper', |
| 'xmlAttribute', |
| 'eventstream', |
| 'event', |
| 'eventheader', |
| 'eventpayload', |
| 'jsonvalue', |
| 'timestampFormat', |
| 'hostLabel', |
| ] |
| METADATA_ATTRS = [ |
| 'required', |
| 'min', |
| 'max', |
| 'pattern', |
| 'sensitive', |
| 'enum', |
| 'idempotencyToken', |
| 'error', |
| 'exception', |
| 'endpointdiscoveryid', |
| 'retryable', |
| 'document', |
| 'union', |
| 'contextParam', |
| 'clientContextParams', |
| 'requiresLength', |
| ] |
| MAP_TYPE = OrderedDict |
|
|
| def __init__(self, shape_name, shape_model, shape_resolver=None): |
| """ |
| |
| :type shape_name: string |
| :param shape_name: The name of the shape. |
| |
| :type shape_model: dict |
| :param shape_model: The shape model. This would be the value |
| associated with the key in the "shapes" dict of the |
| service model (i.e ``model['shapes'][shape_name]``) |
| |
| :type shape_resolver: botocore.model.ShapeResolver |
| :param shape_resolver: A shape resolver object. This is used to |
| resolve references to other shapes. For scalar shape types |
| (string, integer, boolean, etc.), this argument is not |
| required. If a shape_resolver is not provided for a complex |
| type, then a ``ValueError`` will be raised when an attempt |
| to resolve a shape is made. |
| |
| """ |
| self.name = shape_name |
| self.type_name = shape_model['type'] |
| self.documentation = shape_model.get('documentation', '') |
| self._shape_model = shape_model |
| if shape_resolver is None: |
| |
| |
| |
| |
| |
| shape_resolver = UnresolvableShapeMap() |
| self._shape_resolver = shape_resolver |
| self._cache = {} |
|
|
| @CachedProperty |
| def serialization(self): |
| """Serialization information about the shape. |
| |
| This contains information that may be needed for input serialization |
| or response parsing. This can include: |
| |
| * name |
| * queryName |
| * flattened |
| * location |
| * payload |
| * streaming |
| * xmlNamespace |
| * resultWrapper |
| * xmlAttribute |
| * jsonvalue |
| * timestampFormat |
| |
| :rtype: dict |
| :return: Serialization information about the shape. |
| |
| """ |
| model = self._shape_model |
| serialization = {} |
| for attr in self.SERIALIZED_ATTRS: |
| if attr in self._shape_model: |
| serialization[attr] = model[attr] |
| |
| if 'locationName' in serialization: |
| serialization['name'] = serialization.pop('locationName') |
| return serialization |
|
|
| @CachedProperty |
| def metadata(self): |
| """Metadata about the shape. |
| |
| This requires optional information about the shape, including: |
| |
| * min |
| * max |
| * pattern |
| * enum |
| * sensitive |
| * required |
| * idempotencyToken |
| * document |
| * union |
| * contextParam |
| * clientContextParams |
| * requiresLength |
| |
| :rtype: dict |
| :return: Metadata about the shape. |
| |
| """ |
| model = self._shape_model |
| metadata = {} |
| for attr in self.METADATA_ATTRS: |
| if attr in self._shape_model: |
| metadata[attr] = model[attr] |
| return metadata |
|
|
| @CachedProperty |
| def required_members(self): |
| """A list of members that are required. |
| |
| A structure shape can define members that are required. |
| This value will return a list of required members. If there |
| are no required members an empty list is returned. |
| |
| """ |
| return self.metadata.get('required', []) |
|
|
| def _resolve_shape_ref(self, shape_ref): |
| return self._shape_resolver.resolve_shape_ref(shape_ref) |
|
|
| def __repr__(self): |
| return f"<{self.__class__.__name__}({self.name})>" |
|
|
| @property |
| def event_stream_name(self): |
| return None |
|
|
|
|
| class StructureShape(Shape): |
| @CachedProperty |
| def members(self): |
| members = self._shape_model.get('members', self.MAP_TYPE()) |
| |
| |
| |
| |
| |
| |
| shape_members = self.MAP_TYPE() |
| for name, shape_ref in members.items(): |
| shape_members[name] = self._resolve_shape_ref(shape_ref) |
| return shape_members |
|
|
| @CachedProperty |
| def event_stream_name(self): |
| for member_name, member in self.members.items(): |
| if member.serialization.get('eventstream'): |
| return member_name |
| return None |
|
|
| @CachedProperty |
| def error_code(self): |
| if not self.metadata.get('exception', False): |
| return None |
| error_metadata = self.metadata.get("error", {}) |
| code = error_metadata.get("code") |
| if code: |
| return code |
| |
| return self.name |
|
|
| @CachedProperty |
| def is_document_type(self): |
| return self.metadata.get('document', False) |
|
|
| @CachedProperty |
| def is_tagged_union(self): |
| return self.metadata.get('union', False) |
|
|
|
|
| class ListShape(Shape): |
| @CachedProperty |
| def member(self): |
| return self._resolve_shape_ref(self._shape_model['member']) |
|
|
|
|
| class MapShape(Shape): |
| @CachedProperty |
| def key(self): |
| return self._resolve_shape_ref(self._shape_model['key']) |
|
|
| @CachedProperty |
| def value(self): |
| return self._resolve_shape_ref(self._shape_model['value']) |
|
|
|
|
| class StringShape(Shape): |
| @CachedProperty |
| def enum(self): |
| return self.metadata.get('enum', []) |
|
|
|
|
| class StaticContextParameter(NamedTuple): |
| name: str |
| value: Union[bool, str] |
|
|
|
|
| class ContextParameter(NamedTuple): |
| name: str |
| member_name: str |
|
|
|
|
| class ClientContextParameter(NamedTuple): |
| name: str |
| type: str |
| documentation: str |
|
|
|
|
| class ServiceModel: |
| """ |
| |
| :ivar service_description: The parsed service description dictionary. |
| |
| """ |
|
|
| def __init__(self, service_description, service_name=None): |
| """ |
| |
| :type service_description: dict |
| :param service_description: The service description model. This value |
| is obtained from a botocore.loader.Loader, or from directly loading |
| the file yourself:: |
| |
| service_description = json.load( |
| open('/path/to/service-description-model.json')) |
| model = ServiceModel(service_description) |
| |
| :type service_name: str |
| :param service_name: The name of the service. Normally this is |
| the endpoint prefix defined in the service_description. However, |
| you can override this value to provide a more convenient name. |
| This is done in a few places in botocore (ses instead of email, |
| emr instead of elasticmapreduce). If this value is not provided, |
| it will default to the endpointPrefix defined in the model. |
| |
| """ |
| self._service_description = service_description |
| |
| self.metadata = service_description.get('metadata', {}) |
| self._shape_resolver = ShapeResolver( |
| service_description.get('shapes', {}) |
| ) |
| self._signature_version = NOT_SET |
| self._service_name = service_name |
| self._instance_cache = {} |
|
|
| def shape_for(self, shape_name, member_traits=None): |
| return self._shape_resolver.get_shape_by_name( |
| shape_name, member_traits |
| ) |
|
|
| def shape_for_error_code(self, error_code): |
| return self._error_code_cache.get(error_code, None) |
|
|
| @CachedProperty |
| def _error_code_cache(self): |
| error_code_cache = {} |
| for error_shape in self.error_shapes: |
| code = error_shape.error_code |
| error_code_cache[code] = error_shape |
| return error_code_cache |
|
|
| def resolve_shape_ref(self, shape_ref): |
| return self._shape_resolver.resolve_shape_ref(shape_ref) |
|
|
| @CachedProperty |
| def shape_names(self): |
| return list(self._service_description.get('shapes', {})) |
|
|
| @CachedProperty |
| def error_shapes(self): |
| error_shapes = [] |
| for shape_name in self.shape_names: |
| error_shape = self.shape_for(shape_name) |
| if error_shape.metadata.get('exception', False): |
| error_shapes.append(error_shape) |
| return error_shapes |
|
|
| @instance_cache |
| def operation_model(self, operation_name): |
| try: |
| model = self._service_description['operations'][operation_name] |
| except KeyError: |
| raise OperationNotFoundError(operation_name) |
| return OperationModel(model, self, operation_name) |
|
|
| @CachedProperty |
| def documentation(self): |
| return self._service_description.get('documentation', '') |
|
|
| @CachedProperty |
| def operation_names(self): |
| return list(self._service_description.get('operations', [])) |
|
|
| @CachedProperty |
| def service_name(self): |
| """The name of the service. |
| |
| This defaults to the endpointPrefix defined in the service model. |
| However, this value can be overriden when a ``ServiceModel`` is |
| created. If a service_name was not provided when the ``ServiceModel`` |
| was created and if there is no endpointPrefix defined in the |
| service model, then an ``UndefinedModelAttributeError`` exception |
| will be raised. |
| |
| """ |
| if self._service_name is not None: |
| return self._service_name |
| else: |
| return self.endpoint_prefix |
|
|
| @CachedProperty |
| def service_id(self): |
| try: |
| return ServiceId(self._get_metadata_property('serviceId')) |
| except UndefinedModelAttributeError: |
| raise MissingServiceIdError(service_name=self._service_name) |
|
|
| @CachedProperty |
| def signing_name(self): |
| """The name to use when computing signatures. |
| |
| If the model does not define a signing name, this |
| value will be the endpoint prefix defined in the model. |
| """ |
| signing_name = self.metadata.get('signingName') |
| if signing_name is None: |
| signing_name = self.endpoint_prefix |
| return signing_name |
|
|
| @CachedProperty |
| def api_version(self): |
| return self._get_metadata_property('apiVersion') |
|
|
| @CachedProperty |
| def protocol(self): |
| return self._get_metadata_property('protocol') |
|
|
| @CachedProperty |
| def protocols(self): |
| return self._get_metadata_property('protocols') |
|
|
| @CachedProperty |
| def resolved_protocol(self): |
| |
| |
| |
| if self.metadata.get('protocols'): |
| for protocol in PRIORITY_ORDERED_SUPPORTED_PROTOCOLS: |
| if protocol in self.protocols: |
| return protocol |
| raise UnsupportedServiceProtocolsError( |
| botocore_supported_protocols=PRIORITY_ORDERED_SUPPORTED_PROTOCOLS, |
| service_supported_protocols=self.protocols, |
| service=self.service_name, |
| ) |
| |
| |
| return self.protocol |
|
|
| @CachedProperty |
| def endpoint_prefix(self): |
| return self._get_metadata_property('endpointPrefix') |
|
|
| @CachedProperty |
| def endpoint_discovery_operation(self): |
| for operation in self.operation_names: |
| model = self.operation_model(operation) |
| if model.is_endpoint_discovery_operation: |
| return model |
|
|
| @CachedProperty |
| def endpoint_discovery_required(self): |
| for operation in self.operation_names: |
| model = self.operation_model(operation) |
| if ( |
| model.endpoint_discovery is not None |
| and model.endpoint_discovery.get('required') |
| ): |
| return True |
| return False |
|
|
| @CachedProperty |
| def client_context_parameters(self): |
| params = self._service_description.get('clientContextParams', {}) |
| return [ |
| ClientContextParameter( |
| name=param_name, |
| type=param_val['type'], |
| documentation=param_val['documentation'], |
| ) |
| for param_name, param_val in params.items() |
| ] |
|
|
| def _get_metadata_property(self, name): |
| try: |
| return self.metadata[name] |
| except KeyError: |
| raise UndefinedModelAttributeError( |
| f'"{name}" not defined in the metadata of the model: {self}' |
| ) |
|
|
| |
| |
|
|
| @property |
| def signature_version(self): |
| if self._signature_version is NOT_SET: |
| signature_version = self.metadata.get('signatureVersion') |
| self._signature_version = signature_version |
| return self._signature_version |
|
|
| @signature_version.setter |
| def signature_version(self, value): |
| self._signature_version = value |
|
|
| @CachedProperty |
| def is_query_compatible(self): |
| return 'awsQueryCompatible' in self.metadata |
|
|
| def __repr__(self): |
| return f'{self.__class__.__name__}({self.service_name})' |
|
|
|
|
| class OperationModel: |
| def __init__(self, operation_model, service_model, name=None): |
| """ |
| |
| :type operation_model: dict |
| :param operation_model: The operation model. This comes from the |
| service model, and is the value associated with the operation |
| name in the service model (i.e ``model['operations'][op_name]``). |
| |
| :type service_model: botocore.model.ServiceModel |
| :param service_model: The service model associated with the operation. |
| |
| :type name: string |
| :param name: The operation name. This is the operation name exposed to |
| the users of this model. This can potentially be different from |
| the "wire_name", which is the operation name that *must* by |
| provided over the wire. For example, given:: |
| |
| "CreateCloudFrontOriginAccessIdentity":{ |
| "name":"CreateCloudFrontOriginAccessIdentity2014_11_06", |
| ... |
| } |
| |
| The ``name`` would be ``CreateCloudFrontOriginAccessIdentity``, |
| but the ``self.wire_name`` would be |
| ``CreateCloudFrontOriginAccessIdentity2014_11_06``, which is the |
| value we must send in the corresponding HTTP request. |
| |
| """ |
| self._operation_model = operation_model |
| self._service_model = service_model |
| self._api_name = name |
| |
| |
| self._wire_name = operation_model.get('name') |
| self.metadata = service_model.metadata |
| self.http = operation_model.get('http', {}) |
|
|
| @CachedProperty |
| def name(self): |
| if self._api_name is not None: |
| return self._api_name |
| else: |
| return self.wire_name |
|
|
| @property |
| def wire_name(self): |
| """The wire name of the operation. |
| |
| In many situations this is the same value as the |
| ``name``, value, but in some services, the operation name |
| exposed to the user is different from the operation name |
| we send across the wire (e.g cloudfront). |
| |
| Any serialization code should use ``wire_name``. |
| |
| """ |
| return self._operation_model.get('name') |
|
|
| @property |
| def service_model(self): |
| return self._service_model |
|
|
| @CachedProperty |
| def documentation(self): |
| return self._operation_model.get('documentation', '') |
|
|
| @CachedProperty |
| def deprecated(self): |
| return self._operation_model.get('deprecated', False) |
|
|
| @CachedProperty |
| def endpoint_discovery(self): |
| |
| |
| return self._operation_model.get('endpointdiscovery', None) |
|
|
| @CachedProperty |
| def is_endpoint_discovery_operation(self): |
| return self._operation_model.get('endpointoperation', False) |
|
|
| @CachedProperty |
| def input_shape(self): |
| if 'input' not in self._operation_model: |
| |
| |
| return None |
| return self._service_model.resolve_shape_ref( |
| self._operation_model['input'] |
| ) |
|
|
| @CachedProperty |
| def output_shape(self): |
| if 'output' not in self._operation_model: |
| |
| |
| |
| return None |
| return self._service_model.resolve_shape_ref( |
| self._operation_model['output'] |
| ) |
|
|
| @CachedProperty |
| def idempotent_members(self): |
| input_shape = self.input_shape |
| if not input_shape: |
| return [] |
|
|
| return [ |
| name |
| for (name, shape) in input_shape.members.items() |
| if 'idempotencyToken' in shape.metadata |
| and shape.metadata['idempotencyToken'] |
| ] |
|
|
| @CachedProperty |
| def static_context_parameters(self): |
| params = self._operation_model.get('staticContextParams', {}) |
| return [ |
| StaticContextParameter(name=name, value=props.get('value')) |
| for name, props in params.items() |
| ] |
|
|
| @CachedProperty |
| def context_parameters(self): |
| if not self.input_shape: |
| return [] |
|
|
| return [ |
| ContextParameter( |
| name=shape.metadata['contextParam']['name'], |
| member_name=name, |
| ) |
| for name, shape in self.input_shape.members.items() |
| if 'contextParam' in shape.metadata |
| and 'name' in shape.metadata['contextParam'] |
| ] |
|
|
| @CachedProperty |
| def operation_context_parameters(self): |
| return self._operation_model.get('operationContextParams', []) |
|
|
| @CachedProperty |
| def request_compression(self): |
| return self._operation_model.get('requestcompression') |
|
|
| @CachedProperty |
| def auth(self): |
| return self._operation_model.get('auth') |
|
|
| @CachedProperty |
| def auth_type(self): |
| return self._operation_model.get('authtype') |
|
|
| @CachedProperty |
| def resolved_auth_type(self): |
| if self.auth: |
| return resolve_auth_type(self.auth) |
| return self.auth_type |
|
|
| @CachedProperty |
| def unsigned_payload(self): |
| return self._operation_model.get('unsignedPayload') |
|
|
| @CachedProperty |
| def error_shapes(self): |
| shapes = self._operation_model.get("errors", []) |
| return list(self._service_model.resolve_shape_ref(s) for s in shapes) |
|
|
| @CachedProperty |
| def endpoint(self): |
| return self._operation_model.get('endpoint') |
|
|
| @CachedProperty |
| def http_checksum_required(self): |
| return self._operation_model.get('httpChecksumRequired', False) |
|
|
| @CachedProperty |
| def http_checksum(self): |
| return self._operation_model.get('httpChecksum', {}) |
|
|
| @CachedProperty |
| def has_event_stream_input(self): |
| return self.get_event_stream_input() is not None |
|
|
| @CachedProperty |
| def has_event_stream_output(self): |
| return self.get_event_stream_output() is not None |
|
|
| def get_event_stream_input(self): |
| return self._get_event_stream(self.input_shape) |
|
|
| def get_event_stream_output(self): |
| return self._get_event_stream(self.output_shape) |
|
|
| def _get_event_stream(self, shape): |
| """Returns the event stream member's shape if any or None otherwise.""" |
| if shape is None: |
| return None |
| event_name = shape.event_stream_name |
| if event_name: |
| return shape.members[event_name] |
| return None |
|
|
| @CachedProperty |
| def has_streaming_input(self): |
| return self.get_streaming_input() is not None |
|
|
| @CachedProperty |
| def has_streaming_output(self): |
| return self.get_streaming_output() is not None |
|
|
| def get_streaming_input(self): |
| return self._get_streaming_body(self.input_shape) |
|
|
| def get_streaming_output(self): |
| return self._get_streaming_body(self.output_shape) |
|
|
| def _get_streaming_body(self, shape): |
| """Returns the streaming member's shape if any; or None otherwise.""" |
| if shape is None: |
| return None |
| payload = shape.serialization.get('payload') |
| if payload is not None: |
| payload_shape = shape.members[payload] |
| if payload_shape.type_name == 'blob': |
| return payload_shape |
| return None |
|
|
| def __repr__(self): |
| return f'{self.__class__.__name__}(name={self.name})' |
|
|
|
|
| class ShapeResolver: |
| """Resolves shape references.""" |
|
|
| |
| SHAPE_CLASSES = { |
| 'structure': StructureShape, |
| 'list': ListShape, |
| 'map': MapShape, |
| 'string': StringShape, |
| } |
|
|
| def __init__(self, shape_map): |
| self._shape_map = shape_map |
| self._shape_cache = {} |
|
|
| def get_shape_by_name(self, shape_name, member_traits=None): |
| try: |
| shape_model = self._shape_map[shape_name] |
| except KeyError: |
| raise NoShapeFoundError(shape_name) |
| try: |
| shape_cls = self.SHAPE_CLASSES.get(shape_model['type'], Shape) |
| except KeyError: |
| raise InvalidShapeError( |
| f"Shape is missing required key 'type': {shape_model}" |
| ) |
| if member_traits: |
| shape_model = shape_model.copy() |
| shape_model.update(member_traits) |
| result = shape_cls(shape_name, shape_model, self) |
| return result |
|
|
| def resolve_shape_ref(self, shape_ref): |
| |
| |
| |
| |
| |
| if len(shape_ref) == 1 and 'shape' in shape_ref: |
| |
| |
| |
| return self.get_shape_by_name(shape_ref['shape']) |
| else: |
| member_traits = shape_ref.copy() |
| try: |
| shape_name = member_traits.pop('shape') |
| except KeyError: |
| raise InvalidShapeReferenceError( |
| f"Invalid model, missing shape reference: {shape_ref}" |
| ) |
| return self.get_shape_by_name(shape_name, member_traits) |
|
|
|
|
| class UnresolvableShapeMap: |
| """A ShapeResolver that will throw ValueErrors when shapes are resolved.""" |
|
|
| def get_shape_by_name(self, shape_name, member_traits=None): |
| raise ValueError( |
| f"Attempted to lookup shape '{shape_name}', but no shape map was provided." |
| ) |
|
|
| def resolve_shape_ref(self, shape_ref): |
| raise ValueError( |
| f"Attempted to resolve shape '{shape_ref}', but no shape " |
| f"map was provided." |
| ) |
|
|
|
|
| class DenormalizedStructureBuilder: |
| """Build a StructureShape from a denormalized model. |
| |
| This is a convenience builder class that makes it easy to construct |
| ``StructureShape``s based on a denormalized model. |
| |
| It will handle the details of creating unique shape names and creating |
| the appropriate shape map needed by the ``StructureShape`` class. |
| |
| Example usage:: |
| |
| builder = DenormalizedStructureBuilder() |
| shape = builder.with_members({ |
| 'A': { |
| 'type': 'structure', |
| 'members': { |
| 'B': { |
| 'type': 'structure', |
| 'members': { |
| 'C': { |
| 'type': 'string', |
| } |
| } |
| } |
| } |
| } |
| }).build_model() |
| # ``shape`` is now an instance of botocore.model.StructureShape |
| |
| :type dict_type: class |
| :param dict_type: The dictionary type to use, allowing you to opt-in |
| to using OrderedDict or another dict type. This can |
| be particularly useful for testing when order |
| matters, such as for documentation. |
| |
| """ |
|
|
| SCALAR_TYPES = ( |
| 'string', |
| 'integer', |
| 'boolean', |
| 'blob', |
| 'float', |
| 'timestamp', |
| 'long', |
| 'double', |
| 'char', |
| ) |
|
|
| def __init__(self, name=None): |
| self.members = OrderedDict() |
| self._name_generator = ShapeNameGenerator() |
| if name is None: |
| self.name = self._name_generator.new_shape_name('structure') |
|
|
| def with_members(self, members): |
| """ |
| |
| :type members: dict |
| :param members: The denormalized members. |
| |
| :return: self |
| |
| """ |
| self._members = members |
| return self |
|
|
| def build_model(self): |
| """Build the model based on the provided members. |
| |
| :rtype: botocore.model.StructureShape |
| :return: The built StructureShape object. |
| |
| """ |
| shapes = OrderedDict() |
| denormalized = { |
| 'type': 'structure', |
| 'members': self._members, |
| } |
| self._build_model(denormalized, shapes, self.name) |
| resolver = ShapeResolver(shape_map=shapes) |
| return StructureShape( |
| shape_name=self.name, |
| shape_model=shapes[self.name], |
| shape_resolver=resolver, |
| ) |
|
|
| def _build_model(self, model, shapes, shape_name): |
| if model['type'] == 'structure': |
| shapes[shape_name] = self._build_structure(model, shapes) |
| elif model['type'] == 'list': |
| shapes[shape_name] = self._build_list(model, shapes) |
| elif model['type'] == 'map': |
| shapes[shape_name] = self._build_map(model, shapes) |
| elif model['type'] in self.SCALAR_TYPES: |
| shapes[shape_name] = self._build_scalar(model) |
| else: |
| raise InvalidShapeError(f"Unknown shape type: {model['type']}") |
|
|
| def _build_structure(self, model, shapes): |
| members = OrderedDict() |
| shape = self._build_initial_shape(model) |
| shape['members'] = members |
|
|
| for name, member_model in model.get('members', OrderedDict()).items(): |
| member_shape_name = self._get_shape_name(member_model) |
| members[name] = {'shape': member_shape_name} |
| self._build_model(member_model, shapes, member_shape_name) |
| return shape |
|
|
| def _build_list(self, model, shapes): |
| member_shape_name = self._get_shape_name(model) |
| shape = self._build_initial_shape(model) |
| shape['member'] = {'shape': member_shape_name} |
| self._build_model(model['member'], shapes, member_shape_name) |
| return shape |
|
|
| def _build_map(self, model, shapes): |
| key_shape_name = self._get_shape_name(model['key']) |
| value_shape_name = self._get_shape_name(model['value']) |
| shape = self._build_initial_shape(model) |
| shape['key'] = {'shape': key_shape_name} |
| shape['value'] = {'shape': value_shape_name} |
| self._build_model(model['key'], shapes, key_shape_name) |
| self._build_model(model['value'], shapes, value_shape_name) |
| return shape |
|
|
| def _build_initial_shape(self, model): |
| shape = { |
| 'type': model['type'], |
| } |
| if 'documentation' in model: |
| shape['documentation'] = model['documentation'] |
| for attr in Shape.METADATA_ATTRS: |
| if attr in model: |
| shape[attr] = model[attr] |
| return shape |
|
|
| def _build_scalar(self, model): |
| return self._build_initial_shape(model) |
|
|
| def _get_shape_name(self, model): |
| if 'shape_name' in model: |
| return model['shape_name'] |
| else: |
| return self._name_generator.new_shape_name(model['type']) |
|
|
|
|
| class ShapeNameGenerator: |
| """Generate unique shape names for a type. |
| |
| This class can be used in conjunction with the DenormalizedStructureBuilder |
| to generate unique shape names for a given type. |
| |
| """ |
|
|
| def __init__(self): |
| self._name_cache = defaultdict(int) |
|
|
| def new_shape_name(self, type_name): |
| """Generate a unique shape name. |
| |
| This method will guarantee a unique shape name each time it is |
| called with the same type. |
| |
| :: |
| |
| >>> s = ShapeNameGenerator() |
| >>> s.new_shape_name('structure') |
| 'StructureType1' |
| >>> s.new_shape_name('structure') |
| 'StructureType2' |
| >>> s.new_shape_name('list') |
| 'ListType1' |
| >>> s.new_shape_name('list') |
| 'ListType2' |
| |
| |
| :type type_name: string |
| :param type_name: The type name (structure, list, map, string, etc.) |
| |
| :rtype: string |
| :return: A unique shape name for the given type |
| |
| """ |
| self._name_cache[type_name] += 1 |
| current_index = self._name_cache[type_name] |
| return f'{type_name.capitalize()}Type{current_index}' |
|
|