# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# https://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import logging
from botocore import xform_name
from boto3.docs.docstring import ActionDocstring
from boto3.utils import inject_attribute
from .model import Action
from .params import create_request_parameters
from .response import RawHandler, ResourceHandler
logger = logging.getLogger(__name__)
[docs]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)
[docs]class BatchAction(ServiceAction):
"""
An action which operates on a batch of items in a collection, typically
a single page of results from the collection's underlying service
operation call. For example, this allows you to delete up to 999
S3 objects in a single operation rather than calling ``.delete()`` on
each one individually.
: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 __call__(self, parent, *args, **kwargs):
"""
Perform the batch action's operation on every page of results
from the collection.
:type parent:
:py:class:`~boto3.resources.collection.ResourceCollection`
:param parent: The collection iterator to which this action
is attached.
:rtype: list(dict)
:return: A list of low-level response dicts from each call.
"""
service_name = None
client = None
responses = []
operation_name = xform_name(self._action_model.request.operation)
# Unlike the simple action above, a batch action must operate
# on batches (or pages) of items. So we get each page, construct
# the necessary parameters and call the batch operation.
for page in parent.pages():
params = {}
for index, resource in enumerate(page):
# There is no public interface to get a service name
# or low-level client from a collection, so we get
# these from the first resource in the collection.
if service_name is None:
service_name = resource.meta.service_name
if client is None:
client = resource.meta.client
create_request_parameters(
resource,
self._action_model.request,
params=params,
index=index,
)
if not params:
# There are no items, no need to make a call.
break
params.update(kwargs)
logger.debug(
'Calling %s:%s with %r', service_name, operation_name, params
)
response = getattr(client, operation_name)(*args, **params)
logger.debug('Response: %r', response)
responses.append(self._response_handler(parent, params, response))
return responses
[docs]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)
[docs]class CustomModeledAction:
"""A custom, modeled action to inject into a resource."""
def __init__(self, action_name, action_model, function, event_emitter):
"""
:type action_name: str
:param action_name: The name of the action to inject, e.g.
'delete_tags'
:type action_model: dict
:param action_model: A JSON definition of the action, as if it were
part of the resource model.
:type function: function
:param function: The function to perform when the action is called.
The first argument should be 'self', which will be the resource
the function is to be called on.
:type event_emitter: :py:class:`botocore.hooks.BaseEventHooks`
:param event_emitter: The session event emitter.
"""
self.name = action_name
self.model = action_model
self.function = function
self.emitter = event_emitter
[docs] def inject(self, class_attributes, service_context, event_name, **kwargs):
resource_name = event_name.rsplit(".")[-1]
action = Action(self.name, self.model, {})
self.function.__name__ = self.name
self.function.__doc__ = ActionDocstring(
resource_name=resource_name,
event_emitter=self.emitter,
action_model=action,
service_model=service_context.service_model,
include_signature=False,
)
inject_attribute(class_attributes, self.name, self.function)