class ActionDocstring(LazyLoadedDocstring):
class LoadReloadDocstring(LazyLoadedDocstring):
class SubResourceDocstring(LazyLoadedDocstring):
class AttributeDocstring(LazyLoadedDocstring):
class IdentifierDocstring(LazyLoadedDocstring):
class ReferenceDocstring(LazyLoadedDocstring):
class CollectionDocstring(LazyLoadedDocstring):
class CollectionMethodDocstring(LazyLoadedDocstring):
class BatchActionDocstring(LazyLoadedDocstring):
class ResourceWaiterDocstring(LazyLoadedDocstring):
    def _write_docstring(self, *args, **kwargs):
    def _write_docstring(self, *args, **kwargs):
    def _write_docstring(self, *args, **kwargs):
    def _write_docstring(self, *args, **kwargs):
    def _write_docstring(self, *args, **kwargs):
    def _write_docstring(self, *args, **kwargs):
    def _write_docstring(self, *args, **kwargs):
    def _write_docstring(self, *args, **kwargs):
    def _write_docstring(self, *args, **kwargs):
    def _write_docstring(self, *args, **kwargs): class ResourceLoadException(Boto3Error):
    pass class ServiceAction:
    """
    A class representing a callable action on a resource, for example
    ``sqs.get_queue_by_name(...)`` or ``s3.Bucket('foo').delete()``.
    The action may construct parameters from existing resource identifiers
    and may return either a raw response or a new resource instance.

    :type action_model: :py:class`~boto3.resources.model.Action`
    :param action_model: The action model.

    :type factory: ResourceFactory
    :param factory: The factory that created the resource class to which
                    this action is attached.

    :type service_context: :py:class:`~boto3.utils.ServiceContext`
    :param service_context: Context about the AWS service
    """

    def __init__(self, action_model, factory=None, service_context=None):
        self._action_model = action_model

        # In the simplest case we just return the response, but if a
        # resource is defined, then we must create these before returning.
        resource_response_model = action_model.resource
        if resource_response_model:
            self._response_handler = ResourceHandler(
                search_path=resource_response_model.path,
                factory=factory,
                resource_model=resource_response_model,
                service_context=service_context,
                operation_name=action_model.request.operation,
            )
        else:
            self._response_handler = RawHandler(action_model.path)

    def __call__(self, parent, *args, **kwargs):
        """
        Perform the action's request operation after building operation
        parameters and build any defined resources from the response.

        :type parent: :py:class:`~boto3.resources.base.ServiceResource`
        :param parent: The resource instance to which this action is attached.
        :rtype: dict or ServiceResource or list(ServiceResource)
        :return: The response, either as a raw dict or resource instance(s).
        """
        operation_name = xform_name(self._action_model.request.operation)

        # First, build predefined params and then update with the
        # user-supplied kwargs, which allows overriding the pre-built
        # params if needed.
        params = create_request_parameters(parent, self._action_model.request)
        params.update(kwargs)

        logger.debug(
            'Calling %s:%s with %r',
            parent.meta.service_name,
            operation_name,
            params,
        )

        response = getattr(parent.meta.client, operation_name)(*args, **params)

        logger.debug('Response: %r', response)

        return self._response_handler(parent, params, response) class WaiterAction:
    """
    A class representing a callable waiter action on a resource, for example
    ``s3.Bucket('foo').wait_until_bucket_exists()``.
    The waiter action may construct parameters from existing resource
    identifiers.

    :type waiter_model: :py:class`~boto3.resources.model.Waiter`
    :param waiter_model: The action waiter.
    :type waiter_resource_name: string
    :param waiter_resource_name: The name of the waiter action for the
                                 resource. It usually begins with a
                                 ``wait_until_``
    """

    def __init__(self, waiter_model, waiter_resource_name):
        self._waiter_model = waiter_model
        self._waiter_resource_name = waiter_resource_name

    def __call__(self, parent, *args, **kwargs):
        """
        Perform the wait operation after building operation
        parameters.

        :type parent: :py:class:`~boto3.resources.base.ServiceResource`
        :param parent: The resource instance to which this action is attached.
        """
        client_waiter_name = xform_name(self._waiter_model.waiter_name)

        # First, build predefined params and then update with the
        # user-supplied kwargs, which allows overriding the pre-built
        # params if needed.
        params = create_request_parameters(parent, self._waiter_model)
        params.update(kwargs)

        logger.debug(
            'Calling %s:%s with %r',
            parent.meta.service_name,
            self._waiter_resource_name,
            params,
        )

        client = parent.meta.client
        waiter = client.get_waiter(client_waiter_name)
        response = waiter.wait(**params)

        logger.debug('Response: %r', response) class ResourceMeta:
    """
    An object containing metadata about a resource.
    """

    def __init__(
        self,
        service_name,
        identifiers=None,
        client=None,
        data=None,
        resource_model=None,
    ):
        #: (``string``) The service name, e.g. 's3'
        self.service_name = service_name

        if identifiers is None:
            identifiers = []
        #: (``list``) List of identifier names
        self.identifiers = identifiers

        #: (:py:class:`~botocore.client.BaseClient`) Low-level Botocore client
        self.client = client
        #: (``dict``) Loaded resource data attributes
        self.data = data

        # The resource model for that resource
        self.resource_model = resource_model

    def __repr__(self):
        return 'ResourceMeta(\'{}\', identifiers={})'.format(
            self.service_name, self.identifiers
        )

    def __eq__(self, other):
        # Two metas are equal if their components are all equal
        if other.__class__.__name__ != self.__class__.__name__:
            return False

        return self.__dict__ == other.__dict__

    def copy(self):
        """
        Create a copy of this metadata object.
        """
        params = self.__dict__.copy()
        service_name = params.pop('service_name')
        return ResourceMeta(service_name, **params) class ServiceResource:
    """
    A base class for resources.

    :type client: botocore.client
    :param client: A low-level Botocore client instance
    """

    meta = None
    """
    Stores metadata about this resource instance, such as the
    ``service_name``, the low-level ``client`` and any cached ``data``
    from when the instance was hydrated. For example::

        # Get a low-level client from a resource instance
        client = resource.meta.client
        response = client.operation(Param='foo')

        # Print the resource instance's service short name
        print(resource.meta.service_name)

    See :py:class:`ResourceMeta` for more information.
    """

    def __init__(self, *args, **kwargs):
        # Always work on a copy of meta, otherwise we would affect other
        # instances of the same subclass.
        self.meta = self.meta.copy()

        # Create a default client if none was passed
        if kwargs.get('client') is not None:
            self.meta.client = kwargs.get('client')
        else:
            self.meta.client = boto3.client(self.meta.service_name)

        # Allow setting identifiers as positional arguments in the order
        # in which they were defined in the ResourceJSON.
        for i, value in enumerate(args):
            setattr(self, '_' + self.meta.identifiers[i], value)

        # Allow setting identifiers via keyword arguments. Here we need
        # extra logic to ignore other keyword arguments like ``client``.
        for name, value in kwargs.items():
            if name == 'client':
                continue

            if name not in self.meta.identifiers:
                raise ValueError(f'Unknown keyword argument: {name}')

            setattr(self, '_' + name, value)

        # Validate that all identifiers have been set.
        for identifier in self.meta.identifiers:
            if getattr(self, identifier) is None:
                raise ValueError(f'Required parameter {identifier} not set')

    def __repr__(self):
        identifiers = []
        for identifier in self.meta.identifiers:
            identifiers.append(
                f'{identifier}={repr(getattr(self, identifier))}'
            )
        return "{}({})".format(
            self.__class__.__name__,
            ', '.join(identifiers),
        )

    def __eq__(self, other):
        # Should be instances of the same resource class
        if other.__class__.__name__ != self.__class__.__name__:
            return False

        # Each of the identifiers should have the same value in both
        # instances, e.g. two buckets need the same name to be equal.
        for identifier in self.meta.identifiers:
            if getattr(self, identifier) != getattr(other, identifier):
                return False

        return True

    def __hash__(self):
        identifiers = []
        for identifier in self.meta.identifiers:
            identifiers.append(getattr(self, identifier))
        return hash((self.__class__.__name__, tuple(identifiers))) class CollectionFactory:
    """
    A factory to create new
    :py:class:`CollectionManager` and :py:class:`ResourceCollection`
    subclasses from a :py:class:`~boto3.resources.model.Collection`
    model. These subclasses include methods to perform batch operations.
    """

    def load_from_definition(
        self, resource_name, collection_model, service_context, event_emitter
    ):
        """
        Loads a collection from a model, creating a new
        :py:class:`CollectionManager` subclass
        with the correct properties and methods, named based on the service
        and resource name, e.g. ec2.InstanceCollectionManager. It also
        creates a new :py:class:`ResourceCollection` subclass which is used
        by the new manager class.

        :type resource_name: string
        :param resource_name: Name of the resource to look up. For services,
                              this should match the ``service_name``.

        :type service_context: :py:class:`~boto3.utils.ServiceContext`
        :param service_context: Context about the AWS service

        :type event_emitter: :py:class:`~botocore.hooks.HierarchialEmitter`
        :param event_emitter: An event emitter

        :rtype: Subclass of :py:class:`CollectionManager`
        :return: The collection class.
        """
        attrs = {}
        collection_name = collection_model.name

        # Create the batch actions for a collection
        self._load_batch_actions(
            attrs,
            resource_name,
            collection_model,
            service_context.service_model,
            event_emitter,
        )
        # Add the documentation to the collection class's methods
        self._load_documented_collection_methods(
            attrs=attrs,
            resource_name=resource_name,
            collection_model=collection_model,
            service_model=service_context.service_model,
            event_emitter=event_emitter,
            base_class=ResourceCollection,
        )

        if service_context.service_name == resource_name:
            cls_name = '{}.{}Collection'.format(
                service_context.service_name, collection_name
            )
        else:
            cls_name = '{}.{}.{}Collection'.format(
                service_context.service_name, resource_name, collection_name
            )

        collection_cls = type(str(cls_name), (ResourceCollection,), attrs)

        # Add the documentation to the collection manager's methods
        self._load_documented_collection_methods(
            attrs=attrs,
            resource_name=resource_name,
            collection_model=collection_model,
            service_model=service_context.service_model,
            event_emitter=event_emitter,
            base_class=CollectionManager,
        )
        attrs['_collection_cls'] = collection_cls
        cls_name += 'Manager'

        return type(str(cls_name), (CollectionManager,), attrs)

    def _load_batch_actions(
        self,
        attrs,
        resource_name,
        collection_model,
        service_model,
        event_emitter,
    ):
        """
        Batch actions on the collection become methods on both
        the collection manager and iterators.
        """
        for action_model in collection_model.batch_actions:
            snake_cased = xform_name(action_model.name)
            attrs[snake_cased] = self._create_batch_action(
                resource_name,
                snake_cased,
                action_model,
                collection_model,
                service_model,
                event_emitter,
            )

    def _load_documented_collection_methods(
        factory_self,
        attrs,
        resource_name,
        collection_model,
        service_model,
        event_emitter,
        base_class,
    ):
        # The base class already has these methods defined. However
        # the docstrings are generic and not based for a particular service
        # or resource. So we override these methods by proxying to the
        # base class's builtin method and adding a docstring
        # that pertains to the resource.

        # A collection's all() method.
        def all(self):
            return base_class.all(self)

        all.__doc__ = docstring.CollectionMethodDocstring(
            resource_name=resource_name,
            action_name='all',
            event_emitter=event_emitter,
            collection_model=collection_model,
            service_model=service_model,
            include_signature=False,
        )
        attrs['all'] = all

        # The collection's filter() method.
        def filter(self, **kwargs):
            return base_class.filter(self, **kwargs)

        filter.__doc__ = docstring.CollectionMethodDocstring(
            resource_name=resource_name,
            action_name='filter',
            event_emitter=event_emitter,
            collection_model=collection_model,
            service_model=service_model,
            include_signature=False,
        )
        attrs['filter'] = filter

        # The collection's limit method.
        def limit(self, count):
            return base_class.limit(self, count)

        limit.__doc__ = docstring.CollectionMethodDocstring(
            resource_name=resource_name,
            action_name='limit',
            event_emitter=event_emitter,
            collection_model=collection_model,
            service_model=service_model,
            include_signature=False,
        )
        attrs['limit'] = limit

        # The collection's page_size method.
        def page_size(self, count):
            return base_class.page_size(self, count)

        page_size.__doc__ = docstring.CollectionMethodDocstring(
            resource_name=resource_name,
            action_name='page_size',
            event_emitter=event_emitter,
            collection_model=collection_model,
            service_model=service_model,
            include_signature=False,
        )
        attrs['page_size'] = page_size

    def _create_batch_action(
        factory_self,
        resource_name,
        snake_cased,
        action_model,
        collection_model,
        service_model,
        event_emitter,
    ):
        """
        Creates a new method which makes a batch operation request
        to the underlying service API.
        """
        action = BatchAction(action_model)

        def batch_action(self, *args, **kwargs):
            return action(self, *args, **kwargs)

        batch_action.__name__ = str(snake_cased)
        batch_action.__doc__ = docstring.BatchActionDocstring(
            resource_name=resource_name,
            event_emitter=event_emitter,
            batch_action_model=action_model,
            service_model=service_model,
            collection_model=collection_model,
            include_signature=False,
        )
        return batch_action class ResourceModel:
    """
    A model representing a resource, defined via a JSON description
    format. A resource has identifiers, attributes, actions,
    sub-resources, references and collections. For more information
    on resources, see :ref:`guide_resources`.

    :type name: string
    :param name: The name of this resource, e.g. ``sqs`` or ``Queue``
    :type definition: dict
    :param definition: The JSON definition
    :type resource_defs: dict
    :param resource_defs: All resources defined in the service
    """

    def __init__(self, name, definition, resource_defs):
        self._definition = definition
        self._resource_defs = resource_defs
        self._renamed = {}

        #: (``string``) The name of this resource
        self.name = name
        #: (``string``) The service shape name for this resource or ``None``
        self.shape = definition.get('shape')

    def load_rename_map(self, shape=None):
        """
        Load a name translation map given a shape. This will set
        up renamed values for any collisions, e.g. if the shape,
        an action, and a subresource all are all named ``foo``
        then the resource will have an action ``foo``, a subresource
        named ``Foo`` and a property named ``foo_attribute``.
        This is the order of precedence, from most important to
        least important:

        * Load action (resource.load)
        * Identifiers
        * Actions
        * Subresources
        * References
        * Collections
        * Waiters
        * Attributes (shape members)

        Batch actions are only exposed on collections, so do not
        get modified here. Subresources use upper camel casing, so
        are unlikely to collide with anything but other subresources.

        Creates a structure like this::

            renames = {
                ('action', 'id'): 'id_action',
                ('collection', 'id'): 'id_collection',
                ('attribute', 'id'): 'id_attribute'
            }

            # Get the final name for an action named 'id'
            name = renames.get(('action', 'id'), 'id')

        :type shape: botocore.model.Shape
        :param shape: The underlying shape for this resource.
        """
        # Meta is a reserved name for resources
        names = {'meta'}
        self._renamed = {}

        if self._definition.get('load'):
            names.add('load')

        for item in self._definition.get('identifiers', []):
            self._load_name_with_category(names, item['name'], 'identifier')

        for name in self._definition.get('actions', {}):
            self._load_name_with_category(names, name, 'action')

        for name, ref in self._get_has_definition().items():
            # Subresources require no data members, just typically
            # identifiers and user input.
            data_required = False
            for identifier in ref['resource']['identifiers']:
                if identifier['source'] == 'data':
                    data_required = True
                    break

            if not data_required:
                self._load_name_with_category(
                    names, name, 'subresource', snake_case=False
                )
            else:
                self._load_name_with_category(names, name, 'reference')

        for name in self._definition.get('hasMany', {}):
            self._load_name_with_category(names, name, 'collection')

        for name in self._definition.get('waiters', {}):
            self._load_name_with_category(
                names, Waiter.PREFIX + name, 'waiter'
            )

        if shape is not None:
            for name in shape.members.keys():
                self._load_name_with_category(names, name, 'attribute')

    def _load_name_with_category(self, names, name, category, snake_case=True):
        """
        Load a name with a given category, possibly renaming it
        if that name is already in use. The name will be stored
        in ``names`` and possibly be set up in ``self._renamed``.

        :type names: set
        :param names: Existing names (Python attributes, properties, or
                      methods) on the resource.
        :type name: string
        :param name: The original name of the value.
        :type category: string
        :param category: The value type, such as 'identifier' or 'action'
        :type snake_case: bool
        :param snake_case: True (default) if the name should be snake cased.
        """
        if snake_case:
            name = xform_name(name)

        if name in names:
            logger.debug(f'Renaming {self.name} {category} {name}')
            self._renamed[(category, name)] = name + '_' + category
            name += '_' + category

            if name in names:
                # This isn't good, let's raise instead of trying to keep
                # renaming this value.
                raise ValueError(
                    'Problem renaming {} {} to {}!'.format(
                        self.name, category, name
                    )
                )

        names.add(name)

    def _get_name(self, category, name, snake_case=True):
        """
        Get a possibly renamed value given a category and name. This
        uses the rename map set up in ``load_rename_map``, so that
        method must be called once first.

        :type category: string
        :param category: The value type, such as 'identifier' or 'action'
        :type name: string
        :param name: The original name of the value
        :type snake_case: bool
        :param snake_case: True (default) if the name should be snake cased.
        :rtype: string
        :return: Either the renamed value if it is set, otherwise the
                 original name.
        """
        if snake_case:
            name = xform_name(name)

        return self._renamed.get((category, name), name)

    def get_attributes(self, shape):
        """
        Get a dictionary of attribute names to original name and shape
        models that represent the attributes of this resource. Looks
        like the following:

            {
                'some_name': ('SomeName', <Shape...>)
            }

        :type shape: botocore.model.Shape
        :param shape: The underlying shape for this resource.
        :rtype: dict
        :return: Mapping of resource attributes.
        """
        attributes = {}
        identifier_names = [i.name for i in self.identifiers]

        for name, member in shape.members.items():
            snake_cased = xform_name(name)
            if snake_cased in identifier_names:
                # Skip identifiers, these are set through other means
                continue
            snake_cased = self._get_name(
                'attribute', snake_cased, snake_case=False
            )
            attributes[snake_cased] = (name, member)

        return attributes

    @property
    def identifiers(self):
        """
        Get a list of resource identifiers.

        :type: list(:py:class:`Identifier`)
        """
        identifiers = []

        for item in self._definition.get('identifiers', []):
            name = self._get_name('identifier', item['name'])
            member_name = item.get('memberName', None)
            if member_name:
                member_name = self._get_name('attribute', member_name)
            identifiers.append(Identifier(name, member_name))

        return identifiers

    @property
    def load(self):
        """
        Get the load action for this resource, if it is defined.

        :type: :py:class:`Action` or ``None``
        """
        action = self._definition.get('load')

        if action is not None:
            action = Action('load', action, self._resource_defs)

        return action

    @property
    def actions(self):
        """
        Get a list of actions for this resource.

        :type: list(:py:class:`Action`)
        """
        actions = []

        for name, item in self._definition.get('actions', {}).items():
            name = self._get_name('action', name)
            actions.append(Action(name, item, self._resource_defs))

        return actions

    @property
    def batch_actions(self):
        """
        Get a list of batch actions for this resource.

        :type: list(:py:class:`Action`)
        """
        actions = []

        for name, item in self._definition.get('batchActions', {}).items():
            name = self._get_name('batch_action', name)
            actions.append(Action(name, item, self._resource_defs))

        return actions

    def _get_has_definition(self):
        """
        Get a ``has`` relationship definition from a model, where the
        service resource model is treated special in that it contains
        a relationship to every resource defined for the service. This
        allows things like ``s3.Object('bucket-name', 'key')`` to
        work even though the JSON doesn't define it explicitly.

        :rtype: dict
        :return: Mapping of names to subresource and reference
                 definitions.
        """
        if self.name not in self._resource_defs:
            # This is the service resource, so let us expose all of
            # the defined resources as subresources.
            definition = {}

            for name, resource_def in self._resource_defs.items():
                # It's possible for the service to have renamed a
                # resource or to have defined multiple names that
                # point to the same resource type, so we need to
                # take that into account.
                found = False
                has_items = self._definition.get('has', {}).items()
                for has_name, has_def in has_items:
                    if has_def.get('resource', {}).get('type') == name:
                        definition[has_name] = has_def
                        found = True

                if not found:
                    # Create a relationship definition and attach it
                    # to the model, such that all identifiers must be
                    # supplied by the user. It will look something like:
                    #
                    # {
                    #   'resource': {
                    #     'type': 'ResourceName',
                    #     'identifiers': [
                    #       {'target': 'Name1', 'source': 'input'},
                    #       {'target': 'Name2', 'source': 'input'},
                    #       ...
                    #     ]
                    #   }
                    # }
                    #
                    fake_has = {'resource': {'type': name, 'identifiers': []}}

                    for identifier in resource_def.get('identifiers', []):
                        fake_has['resource']['identifiers'].append(
                            {'target': identifier['name'], 'source': 'input'}
                        )

                    definition[name] = fake_has
        else:
            definition = self._definition.get('has', {})

        return definition

    def _get_related_resources(self, subresources):
        """
        Get a list of sub-resources or references.

        :type subresources: bool
        :param subresources: ``True`` to get sub-resources, ``False`` to
                             get references.
        :rtype: list(:py:class:`Action`)
        """
        resources = []

        for name, definition in self._get_has_definition().items():
            if subresources:
                name = self._get_name('subresource', name, snake_case=False)
            else:
                name = self._get_name('reference', name)
            action = Action(name, definition, self._resource_defs)

            data_required = False
            for identifier in action.resource.identifiers:
                if identifier.source == 'data':
                    data_required = True
                    break

            if subresources and not data_required:
                resources.append(action)
            elif not subresources and data_required:
                resources.append(action)

        return resources

    @property
    def subresources(self):
        """
        Get a list of sub-resources.

        :type: list(:py:class:`Action`)
        """
        return self._get_related_resources(True)

    @property
    def references(self):
        """
        Get a list of reference resources.

        :type: list(:py:class:`Action`)
        """
        return self._get_related_resources(False)

    @property
    def collections(self):
        """
        Get a list of collections for this resource.

        :type: list(:py:class:`Collection`)
        """
        collections = []

        for name, item in self._definition.get('hasMany', {}).items():
            name = self._get_name('collection', name)
            collections.append(Collection(name, item, self._resource_defs))

        return collections

    @property
    def waiters(self):
        """
        Get a list of waiters for this resource.

        :type: list(:py:class:`Waiter`)
        """
        waiters = []

        for name, item in self._definition.get('waiters', {}).items():
            name = self._get_name('waiter', Waiter.PREFIX + name)
            waiters.append(Waiter(name, item))

        return waiters class ResourceHandler:
    """
    Creates a new resource or list of new resources from the low-level
    response based on the given response resource definition.

    :type search_path: string
    :param search_path: JMESPath expression to search in the response

    :type factory: ResourceFactory
    :param factory: The factory that created the resource class to which
                    this action is attached.

    :type resource_model: :py:class:`~boto3.resources.model.ResponseResource`
    :param resource_model: Response resource model.

    :type service_context: :py:class:`~boto3.utils.ServiceContext`
    :param service_context: Context about the AWS service

    :type operation_name: string
    :param operation_name: Name of the underlying service operation, if it
                           exists.

    :rtype: ServiceResource or list
    :return: New resource instance(s).
    """

    def __init__(
        self,
        search_path,
        factory,
        resource_model,
        service_context,
        operation_name=None,
    ):
        self.search_path = search_path
        self.factory = factory
        self.resource_model = resource_model
        self.operation_name = operation_name
        self.service_context = service_context

    def __call__(self, parent, params, response):
        """
        :type parent: ServiceResource
        :param parent: The resource instance to which this action is attached.
        :type params: dict
        :param params: Request parameters sent to the service.
        :type response: dict
        :param response: Low-level operation response.
        """
        resource_name = self.resource_model.type
        json_definition = self.service_context.resource_json_definitions.get(
            resource_name
        )

        # Load the new resource class that will result from this action.
        resource_cls = self.factory.load_from_definition(
            resource_name=resource_name,
            single_resource_json_definition=json_definition,
            service_context=self.service_context,
        )
        raw_response = response
        search_response = None

        # Anytime a path is defined, it means the response contains the
        # resource's attributes, so resource_data gets set here. It
        # eventually ends up in resource.meta.data, which is where
        # the attribute properties look for data.
        if self.search_path:
            search_response = jmespath.search(self.search_path, raw_response)

        # First, we parse all the identifiers, then create the individual
        # response resources using them. Any identifiers that are lists
        # will have one item consumed from the front of the list for each
        # resource that is instantiated. Items which are not a list will
        # be set as the same value on each new resource instance.
        identifiers = dict(
            build_identifiers(
                self.resource_model.identifiers, parent, params, raw_response
            )
        )

        # If any of the identifiers is a list, then the response is plural
        plural = [v for v in identifiers.values() if isinstance(v, list)]

        if plural:
            response = []

            # The number of items in an identifier that is a list will
            # determine how many resource instances to create.
            for i in range(len(plural[0])):
                # Response item data is *only* available if a search path
                # was given. This prevents accidentally loading unrelated
                # data that may be in the response.
                response_item = None
                if search_response:
                    response_item = search_response[i]
                response.append(
                    self.handle_response_item(
                        resource_cls, parent, identifiers, response_item
                    )
                )
        elif all_not_none(identifiers.values()):
            # All identifiers must always exist, otherwise the resource
            # cannot be instantiated.
            response = self.handle_response_item(
                resource_cls, parent, identifiers, search_response
            )
        else:
            # The response should be empty, but that may mean an
            # empty dict, list, or None based on whether we make
            # a remote service call and what shape it is expected
            # to return.
            response = None
            if self.operation_name is not None:
                # A remote service call was made, so try and determine
                # its shape.
                response = build_empty_response(
                    self.search_path,
                    self.operation_name,
                    self.service_context.service_model,
                )

        return response

    def handle_response_item(
        self, resource_cls, parent, identifiers, resource_data
    ):
        """
        Handles the creation of a single response item by setting
        parameters and creating the appropriate resource instance.

        :type resource_cls: ServiceResource subclass
        :param resource_cls: The resource class to instantiate.
        :type parent: ServiceResource
        :param parent: The resource instance to which this action is attached.
        :type identifiers: dict
        :param identifiers: Map of identifier names to value or values.
        :type resource_data: dict or None
        :param resource_data: Data for resource attributes.
        :rtype: ServiceResource
        :return: New resource instance.
        """
        kwargs = {
            'client': parent.meta.client,
        }

        for name, value in identifiers.items():
            # If value is a list, then consume the next item
            if isinstance(value, list):
                value = value.pop(0)

            kwargs[name] = value

        resource = resource_cls(**kwargs)

        if resource_data is not None:
            resource.meta.data = resource_data

        return resource def build_identifiers(identifiers, parent, params=None, raw_response=None):
    """
    Builds a mapping of identifier names to values based on the
    identifier source location, type, and target. Identifier
    values may be scalars or lists depending on the source type
    and location.

    :type identifiers: list
    :param identifiers: List of :py:class:`~boto3.resources.model.Parameter`
                        definitions
    :type parent: ServiceResource
    :param parent: The resource instance to which this action is attached.
    :type params: dict
    :param params: Request parameters sent to the service.
    :type raw_response: dict
    :param raw_response: Low-level operation response.
    :rtype: list
    :return: An ordered list of ``(name, value)`` identifier tuples.
    """
    results = []

    for identifier in identifiers:
        source = identifier.source
        target = identifier.target

        if source == 'response':
            value = jmespath.search(identifier.path, raw_response)
        elif source == 'requestParameter':
            value = jmespath.search(identifier.path, params)
        elif source == 'identifier':
            value = getattr(parent, xform_name(identifier.name))
        elif source == 'data':
            # If this is a data member then it may incur a load
            # action before returning the value.
            value = get_data_member(parent, identifier.path)
        elif source == 'input':
            # This value is set by the user, so ignore it here
            continue
        else:
            raise NotImplementedError(f'Unsupported source type: {source}')

        results.append((xform_name(target), value))

    return results
import logging
from functools import partial
from ..docs import docstring
from ..exceptions import ResourceLoadException
from .action import ServiceAction, WaiterAction
from .base import ResourceMeta, ServiceResource
from .collection import CollectionFactory
from .model import ResourceModel
from .response import ResourceHandler, build_identifiers
            collection_model=collection_model,
            service_context=service_context,
            event_emitter=factory_self._emitter,
        )

        def get_collection(self):
            return cls(
                collection_model=collection_model,
                parent=self,
                factory=factory_self,
                service_context=service_context,
            )

        get_collection.__name__ = str(collection_model.name)
        get_collection.__doc__ = docstring.CollectionDocstring(
            collection_model=collection_model, include_signature=False
        )
        return property(get_collection)

    def _create_reference(
        factory_self, reference_model, resource_name, service_context
    ):
        """
        Creates a new property on the resource to lazy-load a reference.
        """
        # References are essentially an action with no request
        # or response, so we can re-use the response handlers to
        # build up resources from identifiers and data members.
        handler = ResourceHandler(
            search_path=reference_model.resource.path,
            factory=factory_self,
            resource_model=reference_model.resource,
            service_context=service_context,
        )

        # Are there any identifiers that need access to data members?
        # This is important when building the resource below since
        # it requires the data to be loaded.
        needs_data = any(
            i.source == 'data' for i in reference_model.resource.identifiers
        )

        def get_reference(self):
            # We need to lazy-evaluate the reference to handle circular
            # references between resources. We do this by loading the class
            # when first accessed.
            # This is using a *response handler* so we need to make sure
            # our data is loaded (if possible) and pass that data into
            # the handler as if it were a response. This allows references
            # to have their data loaded properly.
            if needs_data and self.meta.data is None and hasattr(self, 'load'):
                self.load()
            return handler(self, {}, self.meta.data)

        get_reference.__name__ = str(reference_model.name)
        get_reference.__doc__ = docstring.ReferenceDocstring(
            reference_model=reference_model, include_signature=False
        )
        return property(get_reference)

    def _create_class_partial(
        factory_self, subresource_model, resource_name, service_context
    ):
        """
        Creates a new method which acts as a functools.partial, passing
        along the instance's low-level `client` to the new resource
        class' constructor.
        """
        name = subresource_model.resource.type

        def create_resource(self, *args, **kwargs):
            # We need a new method here because we want access to the
            # instance's client.
            positional_args = []

            # We lazy-load the class to handle circular references.
            json_def = service_context.resource_json_definitions.get(name, {})
            resource_cls = factory_self.load_from_definition(
                resource_name=name,
                single_resource_json_definition=json_def,
                service_context=service_context,
            )

            # Assumes that identifiers are in order, which lets you do
            # e.g. ``sqs.Queue('foo').Message('bar')`` to create a new message
            # linked with the ``foo`` queue and which has a ``bar`` receipt
            # handle. If we did kwargs here then future positional arguments
            # would lead to failure.
            identifiers = subresource_model.resource.identifiers
            if identifiers is not None:
                for identifier, value in build_identifiers(identifiers, self):
                    positional_args.append(value)

            return partial(
                resource_cls, *positional_args, client=self.meta.client
            )(*args, **kwargs)

        create_resource.__name__ = str(name)
        create_resource.__doc__ = docstring.SubResourceDocstring(
            resource_name=resource_name,
            sub_resource_model=subresource_model,
            service_model=service_context.service_model,
            include_signature=False,
        )
        return create_resource

    def _create_action(
        factory_self,
        action_model,
        resource_name,
        service_context,
        is_load=False,
    ):
        """
        Creates a new method which makes a request to the underlying
        AWS service.
        """
        # Create the action in in this closure but before the ``do_action``
        # method below is invoked, which allows instances of the resource
        # to share the ServiceAction instance.

        action = ServiceAction(