comparison env/lib/python3.9/site-packages/boto/sdb/connection.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 # Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
2 #
3 # Permission is hereby granted, free of charge, to any person obtaining a
4 # copy of this software and associated documentation files (the
5 # "Software"), to deal in the Software without restriction, including
6 # without limitation the rights to use, copy, modify, merge, publish, dis-
7 # tribute, sublicense, and/or sell copies of the Software, and to permit
8 # persons to whom the Software is furnished to do so, subject to the fol-
9 # lowing conditions:
10 #
11 # The above copyright notice and this permission notice shall be included
12 # in all copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
16 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
17 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 # IN THE SOFTWARE.
21 import xml.sax
22 import threading
23 import boto
24 from boto import handler
25 from boto.connection import AWSQueryConnection
26 from boto.sdb.domain import Domain, DomainMetaData
27 from boto.sdb.item import Item
28 from boto.sdb.regioninfo import SDBRegionInfo
29 from boto.exception import SDBResponseError
30
31 class ItemThread(threading.Thread):
32 """
33 A threaded :class:`Item <boto.sdb.item.Item>` retriever utility class.
34 Retrieved :class:`Item <boto.sdb.item.Item>` objects are stored in the
35 ``items`` instance variable after :py:meth:`run() <run>` is called.
36
37 .. tip:: The item retrieval will not start until
38 the :func:`run() <boto.sdb.connection.ItemThread.run>` method is called.
39 """
40 def __init__(self, name, domain_name, item_names):
41 """
42 :param str name: A thread name. Used for identification.
43 :param str domain_name: The name of a SimpleDB
44 :class:`Domain <boto.sdb.domain.Domain>`
45 :type item_names: string or list of strings
46 :param item_names: The name(s) of the items to retrieve from the specified
47 :class:`Domain <boto.sdb.domain.Domain>`.
48 :ivar list items: A list of items retrieved. Starts as empty list.
49 """
50 super(ItemThread, self).__init__(name=name)
51 #print 'starting %s with %d items' % (name, len(item_names))
52 self.domain_name = domain_name
53 self.conn = SDBConnection()
54 self.item_names = item_names
55 self.items = []
56
57 def run(self):
58 """
59 Start the threaded retrieval of items. Populates the
60 ``items`` list with :class:`Item <boto.sdb.item.Item>` objects.
61 """
62 for item_name in self.item_names:
63 item = self.conn.get_attributes(self.domain_name, item_name)
64 self.items.append(item)
65
66 #boto.set_stream_logger('sdb')
67
68 class SDBConnection(AWSQueryConnection):
69 """
70 This class serves as a gateway to your SimpleDB region (defaults to
71 us-east-1). Methods within allow access to SimpleDB
72 :class:`Domain <boto.sdb.domain.Domain>` objects and their associated
73 :class:`Item <boto.sdb.item.Item>` objects.
74
75 .. tip::
76 While you may instantiate this class directly, it may be easier to
77 go through :py:func:`boto.connect_sdb`.
78 """
79 DefaultRegionName = 'us-east-1'
80 DefaultRegionEndpoint = 'sdb.us-east-1.amazonaws.com'
81 APIVersion = '2009-04-15'
82 ResponseError = SDBResponseError
83
84 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
85 is_secure=True, port=None, proxy=None, proxy_port=None,
86 proxy_user=None, proxy_pass=None, debug=0,
87 https_connection_factory=None, region=None, path='/',
88 converter=None, security_token=None, validate_certs=True,
89 profile_name=None):
90 """
91 For any keywords that aren't documented, refer to the parent class,
92 :py:class:`boto.connection.AWSAuthConnection`. You can avoid having
93 to worry about these keyword arguments by instantiating these objects
94 via :py:func:`boto.connect_sdb`.
95
96 :type region: :class:`boto.sdb.regioninfo.SDBRegionInfo`
97 :keyword region: Explicitly specify a region. Defaults to ``us-east-1``
98 if not specified. You may also specify the region in your ``boto.cfg``:
99
100 .. code-block:: cfg
101
102 [SDB]
103 region = eu-west-1
104
105 """
106 if not region:
107 region_name = boto.config.get('SDB', 'region', self.DefaultRegionName)
108 for reg in boto.sdb.regions():
109 if reg.name == region_name:
110 region = reg
111 break
112
113 self.region = region
114 super(SDBConnection, self).__init__(aws_access_key_id,
115 aws_secret_access_key,
116 is_secure, port, proxy,
117 proxy_port, proxy_user, proxy_pass,
118 self.region.endpoint, debug,
119 https_connection_factory, path,
120 security_token=security_token,
121 validate_certs=validate_certs,
122 profile_name=profile_name)
123 self.box_usage = 0.0
124 self.converter = converter
125 self.item_cls = Item
126
127 def _required_auth_capability(self):
128 return ['sdb']
129
130 def set_item_cls(self, cls):
131 """
132 While the default item class is :py:class:`boto.sdb.item.Item`, this
133 default may be overridden. Use this method to change a connection's
134 item class.
135
136 :param object cls: The new class to set as this connection's item
137 class. See the default item class for inspiration as to what your
138 replacement should/could look like.
139 """
140 self.item_cls = cls
141
142 def _build_name_value_list(self, params, attributes, replace=False,
143 label='Attribute'):
144 keys = sorted(attributes.keys())
145 i = 1
146 for key in keys:
147 value = attributes[key]
148 if isinstance(value, list):
149 for v in value:
150 params['%s.%d.Name' % (label, i)] = key
151 if self.converter:
152 v = self.converter.encode(v)
153 params['%s.%d.Value' % (label, i)] = v
154 if replace:
155 params['%s.%d.Replace' % (label, i)] = 'true'
156 i += 1
157 else:
158 params['%s.%d.Name' % (label, i)] = key
159 if self.converter:
160 value = self.converter.encode(value)
161 params['%s.%d.Value' % (label, i)] = value
162 if replace:
163 params['%s.%d.Replace' % (label, i)] = 'true'
164 i += 1
165
166 def _build_expected_value(self, params, expected_value):
167 params['Expected.1.Name'] = expected_value[0]
168 if expected_value[1] is True:
169 params['Expected.1.Exists'] = 'true'
170 elif expected_value[1] is False:
171 params['Expected.1.Exists'] = 'false'
172 else:
173 params['Expected.1.Value'] = expected_value[1]
174
175 def _build_batch_list(self, params, items, replace=False):
176 item_names = items.keys()
177 i = 0
178 for item_name in item_names:
179 params['Item.%d.ItemName' % i] = item_name
180 j = 0
181 item = items[item_name]
182 if item is not None:
183 attr_names = item.keys()
184 for attr_name in attr_names:
185 value = item[attr_name]
186 if isinstance(value, list):
187 for v in value:
188 if self.converter:
189 v = self.converter.encode(v)
190 params['Item.%d.Attribute.%d.Name' % (i, j)] = attr_name
191 params['Item.%d.Attribute.%d.Value' % (i, j)] = v
192 if replace:
193 params['Item.%d.Attribute.%d.Replace' % (i, j)] = 'true'
194 j += 1
195 else:
196 params['Item.%d.Attribute.%d.Name' % (i, j)] = attr_name
197 if self.converter:
198 value = self.converter.encode(value)
199 params['Item.%d.Attribute.%d.Value' % (i, j)] = value
200 if replace:
201 params['Item.%d.Attribute.%d.Replace' % (i, j)] = 'true'
202 j += 1
203 i += 1
204
205 def _build_name_list(self, params, attribute_names):
206 i = 1
207 attribute_names.sort()
208 for name in attribute_names:
209 params['Attribute.%d.Name' % i] = name
210 i += 1
211
212 def get_usage(self):
213 """
214 Returns the BoxUsage (in USD) accumulated on this specific SDBConnection
215 instance.
216
217 .. tip:: This can be out of date, and should only be treated as a
218 rough estimate. Also note that this estimate only applies to the
219 requests made on this specific connection instance. It is by
220 no means an account-wide estimate.
221
222 :rtype: float
223 :return: The accumulated BoxUsage of all requests made on the connection.
224 """
225 return self.box_usage
226
227 def print_usage(self):
228 """
229 Print the BoxUsage and approximate costs of all requests made on
230 this specific SDBConnection instance.
231
232 .. tip:: This can be out of date, and should only be treated as a
233 rough estimate. Also note that this estimate only applies to the
234 requests made on this specific connection instance. It is by
235 no means an account-wide estimate.
236 """
237 print('Total Usage: %f compute seconds' % self.box_usage)
238 cost = self.box_usage * 0.14
239 print('Approximate Cost: $%f' % cost)
240
241 def get_domain(self, domain_name, validate=True):
242 """
243 Retrieves a :py:class:`boto.sdb.domain.Domain` object whose name
244 matches ``domain_name``.
245
246 :param str domain_name: The name of the domain to retrieve
247 :keyword bool validate: When ``True``, check to see if the domain
248 actually exists. If ``False``, blindly return a
249 :py:class:`Domain <boto.sdb.domain.Domain>` object with the
250 specified name set.
251
252 :raises:
253 :py:class:`boto.exception.SDBResponseError` if ``validate`` is
254 ``True`` and no match could be found.
255
256 :rtype: :py:class:`boto.sdb.domain.Domain`
257 :return: The requested domain
258 """
259 domain = Domain(self, domain_name)
260 if validate:
261 self.select(domain, """select * from `%s` limit 1""" % domain_name)
262 return domain
263
264 def lookup(self, domain_name, validate=True):
265 """
266 Lookup an existing SimpleDB domain. This differs from
267 :py:meth:`get_domain` in that ``None`` is returned if ``validate`` is
268 ``True`` and no match was found (instead of raising an exception).
269
270 :param str domain_name: The name of the domain to retrieve
271
272 :param bool validate: If ``True``, a ``None`` value will be returned
273 if the specified domain can't be found. If ``False``, a
274 :py:class:`Domain <boto.sdb.domain.Domain>` object will be dumbly
275 returned, regardless of whether it actually exists.
276
277 :rtype: :class:`boto.sdb.domain.Domain` object or ``None``
278 :return: The Domain object or ``None`` if the domain does not exist.
279 """
280 try:
281 domain = self.get_domain(domain_name, validate)
282 except:
283 domain = None
284 return domain
285
286 def get_all_domains(self, max_domains=None, next_token=None):
287 """
288 Returns a :py:class:`boto.resultset.ResultSet` containing
289 all :py:class:`boto.sdb.domain.Domain` objects associated with
290 this connection's Access Key ID.
291
292 :keyword int max_domains: Limit the returned
293 :py:class:`ResultSet <boto.resultset.ResultSet>` to the specified
294 number of members.
295 :keyword str next_token: A token string that was returned in an
296 earlier call to this method as the ``next_token`` attribute
297 on the returned :py:class:`ResultSet <boto.resultset.ResultSet>`
298 object. This attribute is set if there are more than Domains than
299 the value specified in the ``max_domains`` keyword. Pass the
300 ``next_token`` value from you earlier query in this keyword to
301 get the next 'page' of domains.
302 """
303 params = {}
304 if max_domains:
305 params['MaxNumberOfDomains'] = max_domains
306 if next_token:
307 params['NextToken'] = next_token
308 return self.get_list('ListDomains', params, [('DomainName', Domain)])
309
310 def create_domain(self, domain_name):
311 """
312 Create a SimpleDB domain.
313
314 :type domain_name: string
315 :param domain_name: The name of the new domain
316
317 :rtype: :class:`boto.sdb.domain.Domain` object
318 :return: The newly created domain
319 """
320 params = {'DomainName': domain_name}
321 d = self.get_object('CreateDomain', params, Domain)
322 d.name = domain_name
323 return d
324
325 def get_domain_and_name(self, domain_or_name):
326 """
327 Given a ``str`` or :class:`boto.sdb.domain.Domain`, return a
328 ``tuple`` with the following members (in order):
329
330 * In instance of :class:`boto.sdb.domain.Domain` for the requested
331 domain
332 * The domain's name as a ``str``
333
334 :type domain_or_name: ``str`` or :class:`boto.sdb.domain.Domain`
335 :param domain_or_name: The domain or domain name to get the domain
336 and name for.
337
338 :raises: :class:`boto.exception.SDBResponseError` when an invalid
339 domain name is specified.
340
341 :rtype: tuple
342 :return: A ``tuple`` with contents outlined as per above.
343 """
344 if (isinstance(domain_or_name, Domain)):
345 return (domain_or_name, domain_or_name.name)
346 else:
347 return (self.get_domain(domain_or_name), domain_or_name)
348
349 def delete_domain(self, domain_or_name):
350 """
351 Delete a SimpleDB domain.
352
353 .. caution:: This will delete the domain and all items within the domain.
354
355 :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
356 :param domain_or_name: Either the name of a domain or a Domain object
357
358 :rtype: bool
359 :return: True if successful
360
361 """
362 domain, domain_name = self.get_domain_and_name(domain_or_name)
363 params = {'DomainName': domain_name}
364 return self.get_status('DeleteDomain', params)
365
366 def domain_metadata(self, domain_or_name):
367 """
368 Get the Metadata for a SimpleDB domain.
369
370 :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
371 :param domain_or_name: Either the name of a domain or a Domain object
372
373 :rtype: :class:`boto.sdb.domain.DomainMetaData` object
374 :return: The newly created domain metadata object
375 """
376 domain, domain_name = self.get_domain_and_name(domain_or_name)
377 params = {'DomainName': domain_name}
378 d = self.get_object('DomainMetadata', params, DomainMetaData)
379 d.domain = domain
380 return d
381
382 def put_attributes(self, domain_or_name, item_name, attributes,
383 replace=True, expected_value=None):
384 """
385 Store attributes for a given item in a domain.
386
387 :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
388 :param domain_or_name: Either the name of a domain or a Domain object
389
390 :type item_name: string
391 :param item_name: The name of the item whose attributes are being
392 stored.
393
394 :type attribute_names: dict or dict-like object
395 :param attribute_names: The name/value pairs to store as attributes
396
397 :type expected_value: list
398 :param expected_value: If supplied, this is a list or tuple consisting
399 of a single attribute name and expected value. The list can be
400 of the form:
401
402 * ['name', 'value']
403
404 In which case the call will first verify that the attribute "name"
405 of this item has a value of "value". If it does, the delete
406 will proceed, otherwise a ConditionalCheckFailed error will be
407 returned. The list can also be of the form:
408
409 * ['name', True|False]
410
411 which will simply check for the existence (True) or
412 non-existence (False) of the attribute.
413
414 :type replace: bool
415 :param replace: Whether the attribute values passed in will replace
416 existing values or will be added as addition values.
417 Defaults to True.
418
419 :rtype: bool
420 :return: True if successful
421 """
422 domain, domain_name = self.get_domain_and_name(domain_or_name)
423 params = {'DomainName': domain_name,
424 'ItemName': item_name}
425 self._build_name_value_list(params, attributes, replace)
426 if expected_value:
427 self._build_expected_value(params, expected_value)
428 return self.get_status('PutAttributes', params)
429
430 def batch_put_attributes(self, domain_or_name, items, replace=True):
431 """
432 Store attributes for multiple items in a domain.
433
434 :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
435 :param domain_or_name: Either the name of a domain or a Domain object
436
437 :type items: dict or dict-like object
438 :param items: A dictionary-like object. The keys of the dictionary are
439 the item names and the values are themselves dictionaries
440 of attribute names/values, exactly the same as the
441 attribute_names parameter of the scalar put_attributes
442 call.
443
444 :type replace: bool
445 :param replace: Whether the attribute values passed in will replace
446 existing values or will be added as addition values.
447 Defaults to True.
448
449 :rtype: bool
450 :return: True if successful
451 """
452 domain, domain_name = self.get_domain_and_name(domain_or_name)
453 params = {'DomainName': domain_name}
454 self._build_batch_list(params, items, replace)
455 return self.get_status('BatchPutAttributes', params, verb='POST')
456
457 def get_attributes(self, domain_or_name, item_name, attribute_names=None,
458 consistent_read=False, item=None):
459 """
460 Retrieve attributes for a given item in a domain.
461
462 :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
463 :param domain_or_name: Either the name of a domain or a Domain object
464
465 :type item_name: string
466 :param item_name: The name of the item whose attributes are
467 being retrieved.
468
469 :type attribute_names: string or list of strings
470 :param attribute_names: An attribute name or list of attribute names.
471 This parameter is optional. If not supplied, all attributes will
472 be retrieved for the item.
473
474 :type consistent_read: bool
475 :param consistent_read: When set to true, ensures that the most recent
476 data is returned.
477
478 :type item: :class:`boto.sdb.item.Item`
479 :keyword item: Instead of instantiating a new Item object, you may
480 specify one to update.
481
482 :rtype: :class:`boto.sdb.item.Item`
483 :return: An Item with the requested attribute name/values set on it
484 """
485 domain, domain_name = self.get_domain_and_name(domain_or_name)
486 params = {'DomainName': domain_name,
487 'ItemName': item_name}
488 if consistent_read:
489 params['ConsistentRead'] = 'true'
490 if attribute_names:
491 if not isinstance(attribute_names, list):
492 attribute_names = [attribute_names]
493 self.build_list_params(params, attribute_names, 'AttributeName')
494 response = self.make_request('GetAttributes', params)
495 body = response.read()
496 if response.status == 200:
497 if item is None:
498 item = self.item_cls(domain, item_name)
499 h = handler.XmlHandler(item, self)
500 xml.sax.parseString(body, h)
501 return item
502 else:
503 raise SDBResponseError(response.status, response.reason, body)
504
505 def delete_attributes(self, domain_or_name, item_name, attr_names=None,
506 expected_value=None):
507 """
508 Delete attributes from a given item in a domain.
509
510 :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
511 :param domain_or_name: Either the name of a domain or a Domain object
512
513 :type item_name: string
514 :param item_name: The name of the item whose attributes are being
515 deleted.
516
517 :type attributes: dict, list or :class:`boto.sdb.item.Item`
518 :param attributes: Either a list containing attribute names which
519 will cause all values associated with that attribute
520 name to be deleted or a dict or Item containing the
521 attribute names and keys and list of values to
522 delete as the value. If no value is supplied,
523 all attribute name/values for the item will be
524 deleted.
525
526 :type expected_value: list
527 :param expected_value: If supplied, this is a list or tuple consisting
528 of a single attribute name and expected value. The list can be
529 of the form:
530
531 * ['name', 'value']
532
533 In which case the call will first verify that the attribute "name"
534 of this item has a value of "value". If it does, the delete
535 will proceed, otherwise a ConditionalCheckFailed error will be
536 returned. The list can also be of the form:
537
538 * ['name', True|False]
539
540 which will simply check for the existence (True) or
541 non-existence (False) of the attribute.
542
543 :rtype: bool
544 :return: True if successful
545 """
546 domain, domain_name = self.get_domain_and_name(domain_or_name)
547 params = {'DomainName': domain_name,
548 'ItemName': item_name}
549 if attr_names:
550 if isinstance(attr_names, list):
551 self._build_name_list(params, attr_names)
552 elif isinstance(attr_names, dict) or isinstance(attr_names, self.item_cls):
553 self._build_name_value_list(params, attr_names)
554 if expected_value:
555 self._build_expected_value(params, expected_value)
556 return self.get_status('DeleteAttributes', params)
557
558 def batch_delete_attributes(self, domain_or_name, items):
559 """
560 Delete multiple items in a domain.
561
562 :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
563 :param domain_or_name: Either the name of a domain or a Domain object
564
565 :type items: dict or dict-like object
566 :param items: A dictionary-like object. The keys of the dictionary are
567 the item names and the values are either:
568
569 * dictionaries of attribute names/values, exactly the
570 same as the attribute_names parameter of the scalar
571 put_attributes call. The attribute name/value pairs
572 will only be deleted if they match the name/value
573 pairs passed in.
574 * None which means that all attributes associated
575 with the item should be deleted.
576
577 :return: True if successful
578 """
579 domain, domain_name = self.get_domain_and_name(domain_or_name)
580 params = {'DomainName': domain_name}
581 self._build_batch_list(params, items, False)
582 return self.get_status('BatchDeleteAttributes', params, verb='POST')
583
584 def select(self, domain_or_name, query='', next_token=None,
585 consistent_read=False):
586 """
587 Returns a set of Attributes for item names within domain_name that
588 match the query. The query must be expressed in using the SELECT
589 style syntax rather than the original SimpleDB query language.
590 Even though the select request does not require a domain object,
591 a domain object must be passed into this method so the Item objects
592 returned can point to the appropriate domain.
593
594 :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object
595 :param domain_or_name: Either the name of a domain or a Domain object
596
597 :type query: string
598 :param query: The SimpleDB query to be performed.
599
600 :type consistent_read: bool
601 :param consistent_read: When set to true, ensures that the most recent
602 data is returned.
603
604 :rtype: ResultSet
605 :return: An iterator containing the results.
606 """
607 domain, domain_name = self.get_domain_and_name(domain_or_name)
608 params = {'SelectExpression': query}
609 if consistent_read:
610 params['ConsistentRead'] = 'true'
611 if next_token:
612 params['NextToken'] = next_token
613 try:
614 return self.get_list('Select', params, [('Item', self.item_cls)],
615 parent=domain)
616 except SDBResponseError as e:
617 e.body = "Query: %s\n%s" % (query, e.body)
618 raise e