1
# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
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-
11
# The above copyright notice and this permission notice shall be included
12
# in all copies or substantial portions of the Software.
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
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
from boto.resultset import ResultSet
34
class ItemThread(threading.Thread):
36
def __init__(self, name, domain_name, item_names):
37
threading.Thread.__init__(self, name=name)
38
print 'starting %s with %d items' % (name, len(item_names))
39
self.domain_name = domain_name
40
self.conn = SDBConnection()
41
self.item_names = item_names
45
for item_name in self.item_names:
46
item = self.conn.get_attributes(self.domain_name, item_name)
47
self.items.append(item)
49
#boto.set_stream_logger('sdb')
51
class SDBConnection(AWSQueryConnection):
53
DefaultRegionName = 'us-east-1'
54
DefaultRegionEndpoint = 'sdb.amazonaws.com'
55
APIVersion = '2009-04-15'
56
SignatureVersion = '2'
57
ResponseError = SDBResponseError
59
def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
60
is_secure=True, port=None, proxy=None, proxy_port=None,
61
proxy_user=None, proxy_pass=None, debug=0,
62
https_connection_factory=None, region=None, path='/', converter=None):
64
region = SDBRegionInfo(self, self.DefaultRegionName, self.DefaultRegionEndpoint)
66
AWSQueryConnection.__init__(self, aws_access_key_id, aws_secret_access_key,
67
is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
68
self.region.endpoint, debug, https_connection_factory, path)
70
self.converter = converter
73
def set_item_cls(self, cls):
76
def build_name_value_list(self, params, attributes, replace=False,
78
keys = attributes.keys()
82
value = attributes[key]
83
if isinstance(value, list):
85
params['%s.%d.Name'%(label,i)] = key
87
v = self.converter.encode(v)
88
params['%s.%d.Value'%(label,i)] = v
90
params['%s.%d.Replace'%(label,i)] = 'true'
93
params['%s.%d.Name'%(label,i)] = key
95
value = self.converter.encode(value)
96
params['%s.%d.Value'%(label,i)] = value
98
params['%s.%d.Replace'%(label,i)] = 'true'
101
def build_expected_value(self, params, expected_value):
102
params['Expected.1.Name'] = expected_value[0]
103
if expected_value[1] == True:
104
params['Expected.1.Exists'] = 'true'
105
elif expected_value[1] == False:
106
params['Expected.1.Exists'] = 'false'
108
params['Expected.1.Value'] = expected_value[1]
111
def build_batch_list(self, params, items, replace=False):
112
item_names = items.keys()
114
for item_name in item_names:
116
item = items[item_name]
117
attr_names = item.keys()
118
params['Item.%d.ItemName' % i] = item_name
119
for attr_name in attr_names:
120
value = item[attr_name]
121
if isinstance(value, list):
124
v = self.converter.encode(v)
125
params['Item.%d.Attribute.%d.Name' % (i,j)] = attr_name
126
params['Item.%d.Attribute.%d.Value' % (i,j)] = v
128
params['Item.%d.Attribute.%d.Replace' % (i,j)] = 'true'
131
params['Item.%d.Attribute.%d.Name' % (i,j)] = attr_name
133
value = self.converter.encode(value)
134
params['Item.%d.Attribute.%d.Value' % (i,j)] = value
136
params['Item.%d.Attribute.%d.Replace' % (i,j)] = 'true'
140
def build_name_list(self, params, attribute_names):
142
attribute_names.sort()
143
for name in attribute_names:
144
params['Attribute.%d.Name'%i] = name
149
Returns the BoxUsage accumulated on this SDBConnection object.
152
:return: The accumulated BoxUsage of all requests made on the connection.
154
return self.box_usage
156
def print_usage(self):
158
Print the BoxUsage and approximate costs of all requests made on this connection.
160
print 'Total Usage: %f compute seconds' % self.box_usage
161
cost = self.box_usage * 0.14
162
print 'Approximate Cost: $%f' % cost
164
def get_domain(self, domain_name, validate=True):
165
domain = Domain(self, domain_name)
167
self.select(domain, """select * from `%s` limit 1""" % domain_name)
170
def lookup(self, domain_name, validate=True):
172
Lookup an existing SimpleDB domain
174
:type domain_name: string
175
:param domain_name: The name of the new domain
177
:rtype: :class:`boto.sdb.domain.Domain` object or None
178
:return: The Domain object or None if the domain does not exist.
181
domain = self.get_domain(domain_name, validate)
186
def get_all_domains(self, max_domains=None, next_token=None):
189
params['MaxNumberOfDomains'] = max_domains
191
params['NextToken'] = next_token
192
return self.get_list('ListDomains', params, [('DomainName', Domain)])
194
def create_domain(self, domain_name):
196
Create a SimpleDB domain.
198
:type domain_name: string
199
:param domain_name: The name of the new domain
201
:rtype: :class:`boto.sdb.domain.Domain` object
202
:return: The newly created domain
204
params = {'DomainName':domain_name}
205
d = self.get_object('CreateDomain', params, Domain)
209
def get_domain_and_name(self, domain_or_name):
210
if (isinstance(domain_or_name, Domain)):
211
return (domain_or_name, domain_or_name.name)
213
return (self.get_domain(domain_or_name), domain_or_name)
215
def delete_domain(self, domain_or_name):
217
Delete a SimpleDB domain.
219
:type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
220
:param domain_or_name: Either the name of a domain or a Domain object
223
:return: True if successful
225
B{Note:} This will delete the domain and all items within the domain.
227
domain, domain_name = self.get_domain_and_name(domain_or_name)
228
params = {'DomainName':domain_name}
229
return self.get_status('DeleteDomain', params)
231
def domain_metadata(self, domain_or_name):
233
Get the Metadata for a SimpleDB domain.
235
:type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
236
:param domain_or_name: Either the name of a domain or a Domain object
238
:rtype: :class:`boto.sdb.domain.DomainMetaData` object
239
:return: The newly created domain metadata object
241
domain, domain_name = self.get_domain_and_name(domain_or_name)
242
params = {'DomainName':domain_name}
243
d = self.get_object('DomainMetadata', params, DomainMetaData)
247
def put_attributes(self, domain_or_name, item_name, attributes,
248
replace=True, expected_value=None):
250
Store attributes for a given item in a domain.
252
:type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
253
:param domain_or_name: Either the name of a domain or a Domain object
255
:type item_name: string
256
:param item_name: The name of the item whose attributes are being stored.
258
:type attribute_names: dict or dict-like object
259
:param attribute_names: The name/value pairs to store as attributes
261
:type expected_value: list
262
:param expected_value: If supplied, this is a list or tuple consisting
263
of a single attribute name and expected value.
264
The list can be of the form:
266
In which case the call will first verify
267
that the attribute "name" of this item has
268
a value of "value". If it does, the delete
269
will proceed, otherwise a ConditionalCheckFailed
270
error will be returned.
271
The list can also be of the form:
272
* ['name', True|False]
273
which will simply check for the existence (True)
274
or non-existencve (False) of the attribute.
277
:param replace: Whether the attribute values passed in will replace
278
existing values or will be added as addition values.
282
:return: True if successful
284
domain, domain_name = self.get_domain_and_name(domain_or_name)
285
params = {'DomainName' : domain_name,
286
'ItemName' : item_name}
287
self.build_name_value_list(params, attributes, replace)
289
self.build_expected_value(params, expected_value)
290
return self.get_status('PutAttributes', params)
292
def batch_put_attributes(self, domain_or_name, items, replace=True):
294
Store attributes for multiple items in a domain.
296
:type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
297
:param domain_or_name: Either the name of a domain or a Domain object
299
:type items: dict or dict-like object
300
:param items: A dictionary-like object. The keys of the dictionary are
301
the item names and the values are themselves dictionaries
302
of attribute names/values, exactly the same as the
303
attribute_names parameter of the scalar put_attributes
307
:param replace: Whether the attribute values passed in will replace
308
existing values or will be added as addition values.
312
:return: True if successful
314
domain, domain_name = self.get_domain_and_name(domain_or_name)
315
params = {'DomainName' : domain_name}
316
self.build_batch_list(params, items, replace)
317
return self.get_status('BatchPutAttributes', params, verb='POST')
319
def get_attributes(self, domain_or_name, item_name, attribute_names=None,
320
consistent_read=False, item=None):
322
Retrieve attributes for a given item in a domain.
324
:type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
325
:param domain_or_name: Either the name of a domain or a Domain object
327
:type item_name: string
328
:param item_name: The name of the item whose attributes are being retrieved.
330
:type attribute_names: string or list of strings
331
:param attribute_names: An attribute name or list of attribute names. This
332
parameter is optional. If not supplied, all attributes
333
will be retrieved for the item.
335
:type consistent_read: bool
336
:param consistent_read: When set to true, ensures that the most recent
339
:rtype: :class:`boto.sdb.item.Item`
340
:return: An Item mapping type containing the requested attribute name/values
342
domain, domain_name = self.get_domain_and_name(domain_or_name)
343
params = {'DomainName' : domain_name,
344
'ItemName' : item_name}
346
params['ConsistentRead'] = 'true'
348
if not isinstance(attribute_names, list):
349
attribute_names = [attribute_names]
350
self.build_list_params(params, attribute_names, 'AttributeName')
351
response = self.make_request('GetAttributes', params)
352
body = response.read()
353
if response.status == 200:
355
item = self.item_cls(domain, item_name)
356
h = handler.XmlHandler(item, self)
357
xml.sax.parseString(body, h)
360
raise SDBResponseError(response.status, response.reason, body)
362
def delete_attributes(self, domain_or_name, item_name, attr_names=None,
363
expected_value=None):
365
Delete attributes from a given item in a domain.
367
:type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
368
:param domain_or_name: Either the name of a domain or a Domain object
370
:type item_name: string
371
:param item_name: The name of the item whose attributes are being deleted.
373
:type attributes: dict, list or :class:`boto.sdb.item.Item`
374
:param attributes: Either a list containing attribute names which will cause
375
all values associated with that attribute name to be deleted or
376
a dict or Item containing the attribute names and keys and list
377
of values to delete as the value. If no value is supplied,
378
all attribute name/values for the item will be deleted.
380
:type expected_value: list
381
:param expected_value: If supplied, this is a list or tuple consisting
382
of a single attribute name and expected value.
383
The list can be of the form:
385
In which case the call will first verify
386
that the attribute "name" of this item has
387
a value of "value". If it does, the delete
388
will proceed, otherwise a ConditionalCheckFailed
389
error will be returned.
390
The list can also be of the form:
391
* ['name', True|False]
392
which will simply check for the existence (True)
393
or non-existencve (False) of the attribute.
396
:return: True if successful
398
domain, domain_name = self.get_domain_and_name(domain_or_name)
399
params = {'DomainName':domain_name,
400
'ItemName' : item_name}
402
if isinstance(attr_names, list):
403
self.build_name_list(params, attr_names)
404
elif isinstance(attr_names, dict) or isinstance(attr_names, self.item_cls):
405
self.build_name_value_list(params, attr_names)
407
self.build_expected_value(params, expected_value)
408
return self.get_status('DeleteAttributes', params)
410
def select(self, domain_or_name, query='', next_token=None,
411
consistent_read=False):
413
Returns a set of Attributes for item names within domain_name that match the query.
414
The query must be expressed in using the SELECT style syntax rather than the
415
original SimpleDB query language.
416
Even though the select request does not require a domain object, a domain
417
object must be passed into this method so the Item objects returned can
418
point to the appropriate domain.
420
:type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
421
:param domain_or_name: Either the name of a domain or a Domain object
424
:param query: The SimpleDB query to be performed.
426
:type consistent_read: bool
427
:param consistent_read: When set to true, ensures that the most recent
431
:return: An iterator containing the results.
433
domain, domain_name = self.get_domain_and_name(domain_or_name)
434
params = {'SelectExpression' : query}
436
params['ConsistentRead'] = 'true'
438
params['NextToken'] = next_token
439
return self.get_list('Select', params, [('Item', self.item_cls)],