1# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"). You
4# may not use this file except in compliance with the License. A copy of
5# the License is located at
6#
7# https://aws.amazon.com/apache2.0/
8#
9# or in the "license" file accompanying this file. This file is
10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11# ANY KIND, either express or implied. See the License for the specific
12# language governing permissions and limitations under the License.
13
14import logging
15
16from botocore import xform_name
17
18from boto3.docs.docstring import ActionDocstring
19from boto3.utils import inject_attribute
20
21from .model import Action
22from .params import create_request_parameters
23from .response import RawHandler, ResourceHandler
24
25logger = logging.getLogger(__name__)
26
27
28class ServiceAction:
29 """
30 A class representing a callable action on a resource, for example
31 ``sqs.get_queue_by_name(...)`` or ``s3.Bucket('foo').delete()``.
32 The action may construct parameters from existing resource identifiers
33 and may return either a raw response or a new resource instance.
34
35 :type action_model: :py:class`~boto3.resources.model.Action`
36 :param action_model: The action model.
37
38 :type factory: ResourceFactory
39 :param factory: The factory that created the resource class to which
40 this action is attached.
41
42 :type service_context: :py:class:`~boto3.utils.ServiceContext`
43 :param service_context: Context about the AWS service
44 """
45
46 def __init__(self, action_model, factory=None, service_context=None):
47 self._action_model = action_model
48
49 # In the simplest case we just return the response, but if a
50 # resource is defined, then we must create these before returning.
51 resource_response_model = action_model.resource
52 if resource_response_model:
53 self._response_handler = ResourceHandler(
54 search_path=resource_response_model.path,
55 factory=factory,
56 resource_model=resource_response_model,
57 service_context=service_context,
58 operation_name=action_model.request.operation,
59 )
60 else:
61 self._response_handler = RawHandler(action_model.path)
62
63 def __call__(self, parent, *args, **kwargs):
64 """
65 Perform the action's request operation after building operation
66 parameters and build any defined resources from the response.
67
68 :type parent: :py:class:`~boto3.resources.base.ServiceResource`
69 :param parent: The resource instance to which this action is attached.
70 :rtype: dict or ServiceResource or list(ServiceResource)
71 :return: The response, either as a raw dict or resource instance(s).
72 """
73 operation_name = xform_name(self._action_model.request.operation)
74
75 # First, build predefined params and then update with the
76 # user-supplied kwargs, which allows overriding the pre-built
77 # params if needed.
78 params = create_request_parameters(parent, self._action_model.request)
79 params.update(kwargs)
80
81 logger.debug(
82 'Calling %s:%s with %r',
83 parent.meta.service_name,
84 operation_name,
85 params,
86 )
87
88 response = getattr(parent.meta.client, operation_name)(*args, **params)
89
90 logger.debug('Response: %r', response)
91
92 return self._response_handler(parent, params, response)
93
94
95class BatchAction(ServiceAction):
96 """
97 An action which operates on a batch of items in a collection, typically
98 a single page of results from the collection's underlying service
99 operation call. For example, this allows you to delete up to 999
100 S3 objects in a single operation rather than calling ``.delete()`` on
101 each one individually.
102
103 :type action_model: :py:class`~boto3.resources.model.Action`
104 :param action_model: The action model.
105
106 :type factory: ResourceFactory
107 :param factory: The factory that created the resource class to which
108 this action is attached.
109
110 :type service_context: :py:class:`~boto3.utils.ServiceContext`
111 :param service_context: Context about the AWS service
112 """
113
114 def __call__(self, parent, *args, **kwargs):
115 """
116 Perform the batch action's operation on every page of results
117 from the collection.
118
119 :type parent:
120 :py:class:`~boto3.resources.collection.ResourceCollection`
121 :param parent: The collection iterator to which this action
122 is attached.
123 :rtype: list(dict)
124 :return: A list of low-level response dicts from each call.
125 """
126 service_name = None
127 client = None
128 responses = []
129 operation_name = xform_name(self._action_model.request.operation)
130
131 # Unlike the simple action above, a batch action must operate
132 # on batches (or pages) of items. So we get each page, construct
133 # the necessary parameters and call the batch operation.
134 for page in parent.pages():
135 params = {}
136 for index, resource in enumerate(page):
137 # There is no public interface to get a service name
138 # or low-level client from a collection, so we get
139 # these from the first resource in the collection.
140 if service_name is None:
141 service_name = resource.meta.service_name
142 if client is None:
143 client = resource.meta.client
144
145 create_request_parameters(
146 resource,
147 self._action_model.request,
148 params=params,
149 index=index,
150 )
151
152 if not params:
153 # There are no items, no need to make a call.
154 break
155
156 params.update(kwargs)
157
158 logger.debug(
159 'Calling %s:%s with %r', service_name, operation_name, params
160 )
161
162 response = getattr(client, operation_name)(*args, **params)
163
164 logger.debug('Response: %r', response)
165
166 responses.append(self._response_handler(parent, params, response))
167
168 return responses
169
170
171class WaiterAction:
172 """
173 A class representing a callable waiter action on a resource, for example
174 ``s3.Bucket('foo').wait_until_bucket_exists()``.
175 The waiter action may construct parameters from existing resource
176 identifiers.
177
178 :type waiter_model: :py:class`~boto3.resources.model.Waiter`
179 :param waiter_model: The action waiter.
180 :type waiter_resource_name: string
181 :param waiter_resource_name: The name of the waiter action for the
182 resource. It usually begins with a
183 ``wait_until_``
184 """
185
186 def __init__(self, waiter_model, waiter_resource_name):
187 self._waiter_model = waiter_model
188 self._waiter_resource_name = waiter_resource_name
189
190 def __call__(self, parent, *args, **kwargs):
191 """
192 Perform the wait operation after building operation
193 parameters.
194
195 :type parent: :py:class:`~boto3.resources.base.ServiceResource`
196 :param parent: The resource instance to which this action is attached.
197 """
198 client_waiter_name = xform_name(self._waiter_model.waiter_name)
199
200 # First, build predefined params and then update with the
201 # user-supplied kwargs, which allows overriding the pre-built
202 # params if needed.
203 params = create_request_parameters(parent, self._waiter_model)
204 params.update(kwargs)
205
206 logger.debug(
207 'Calling %s:%s with %r',
208 parent.meta.service_name,
209 self._waiter_resource_name,
210 params,
211 )
212
213 client = parent.meta.client
214 waiter = client.get_waiter(client_waiter_name)
215 response = waiter.wait(**params)
216
217 logger.debug('Response: %r', response)
218
219
220class CustomModeledAction:
221 """A custom, modeled action to inject into a resource."""
222
223 def __init__(self, action_name, action_model, function, event_emitter):
224 """
225 :type action_name: str
226 :param action_name: The name of the action to inject, e.g.
227 'delete_tags'
228
229 :type action_model: dict
230 :param action_model: A JSON definition of the action, as if it were
231 part of the resource model.
232
233 :type function: function
234 :param function: The function to perform when the action is called.
235 The first argument should be 'self', which will be the resource
236 the function is to be called on.
237
238 :type event_emitter: :py:class:`botocore.hooks.BaseEventHooks`
239 :param event_emitter: The session event emitter.
240 """
241 self.name = action_name
242 self.model = action_model
243 self.function = function
244 self.emitter = event_emitter
245
246 def inject(self, class_attributes, service_context, event_name, **kwargs):
247 resource_name = event_name.rsplit(".")[-1]
248 action = Action(self.name, self.model, {})
249 self.function.__name__ = self.name
250 self.function.__doc__ = ActionDocstring(
251 resource_name=resource_name,
252 event_emitter=self.emitter,
253 action_model=action,
254 service_model=service_context.service_model,
255 include_signature=False,
256 )
257 inject_attribute(class_attributes, self.name, self.function)