diff options
Diffstat (limited to 'boto/dynamodb/layer2.py')
-rw-r--r-- | boto/dynamodb/layer2.py | 234 |
1 files changed, 132 insertions, 102 deletions
diff --git a/boto/dynamodb/layer2.py b/boto/dynamodb/layer2.py index 2f8568af..0d8d5bba 100644 --- a/boto/dynamodb/layer2.py +++ b/boto/dynamodb/layer2.py @@ -15,7 +15,7 @@ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. @@ -25,9 +25,10 @@ from boto.dynamodb.layer1 import Layer1 from boto.dynamodb.table import Table from boto.dynamodb.schema import Schema from boto.dynamodb.item import Item -from boto.dynamodb.batch import BatchList +from boto.dynamodb.batch import BatchList, BatchWriteList from boto.dynamodb.types import get_dynamodb_type, dynamize_value, convert_num + def item_object_hook(dct): """ A custom object hook for use when decoding JSON item bodys. @@ -46,6 +47,60 @@ def item_object_hook(dct): return set(map(convert_num, dct['NS'])) return dct +def table_generator(tgen): + """ + A low-level generator used to page through results from + query and scan operations. This is used by + :class:`boto.dynamodb.layer2.TableGenerator` and is not intended + to be used outside of that context. + """ + response = True + n = 0 + while response: + if tgen.max_results and n == tgen.max_results: + break + if response is True: + pass + elif 'LastEvaluatedKey' in response: + lek = response['LastEvaluatedKey'] + esk = tgen.table.layer2.dynamize_last_evaluated_key(lek) + tgen.kwargs['exclusive_start_key'] = esk + else: + break + response = tgen.callable(**tgen.kwargs) + if 'ConsumedCapacityUnits' in response: + tgen.consumed_units += response['ConsumedCapacityUnits'] + for item in response['Items']: + if tgen.max_results and n == tgen.max_results: + break + yield tgen.item_class(tgen.table, attrs=item) + n += 1 + + +class TableGenerator: + """ + This is an object that wraps up the table_generator function. + The only real reason to have this is that we want to be able + to accumulate and return the ConsumedCapacityUnits element that + is part of each response. + + :ivar consumed_units: An integer that holds the number of + ConsumedCapacityUnits accumulated thus far for this + generator. + """ + + def __init__(self, table, callable, max_results, item_class, kwargs): + self.table = table + self.callable = callable + self.max_results = max_results + self.item_class = item_class + self.kwargs = kwargs + self.consumed_units = 0 + + def __iter__(self): + return table_generator(self) + + class Layer2(object): def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, @@ -53,7 +108,7 @@ class Layer2(object): host=None, debug=0, session_token=None, region=None): self.layer1 = Layer1(aws_access_key_id, aws_secret_access_key, is_secure, port, proxy, proxy_port, - host, debug, session_token, region) + debug, session_token, region) def dynamize_attribute_updates(self, pending_updates): """ @@ -131,32 +186,6 @@ class Layer2(object): d['RangeKeyElement'] = dynamize_value(range_key) return d - def dynamize_request_items(self, batch_list): - """ - Convert a request_items parameter into the data structure - required for Layer1. - """ - d = None - if batch_list: - d = {} - for batch in batch_list: - batch_dict = {} - key_list = [] - for key in batch.keys: - if isinstance(key, tuple): - hash_key, range_key = key - else: - hash_key = key - range_key = None - k = self.build_key_from_values(batch.table.schema, - hash_key, range_key) - key_list.append(k) - batch_dict['Keys'] = key_list - if batch.attributes_to_get: - batch_dict['AttributesToGet'] = batch.attributes_to_get - d[batch.table.name] = batch_dict - return d - def build_key_from_values(self, schema, hash_key, range_key=None): """ Build a Key structure to be used for accessing items @@ -198,6 +227,13 @@ class Layer2(object): """ return BatchList(self) + def new_batch_write_list(self): + """ + Return a new, empty :class:`boto.dynamodb.batch.BatchWriteList` + object. + """ + return BatchWriteList(self) + def list_tables(self, limit=None): """ Return a list of the names of all tables associated with the @@ -239,23 +275,24 @@ class Layer2(object): return Table(self, response) lookup = get_table + def create_table(self, name, schema, read_units, write_units): """ Create a new Amazon DynamoDB table. - + :type name: str :param name: The name of the desired table. :type schema: :class:`boto.dynamodb.schema.Schema` :param schema: The Schema object that defines the schema used by this table. - + :type read_units: int :param read_units: The value for ReadCapacityUnits. - + :type write_units: int :param write_units: The value for WriteCapacityUnits. - + :rtype: :class:`boto.dynamodb.table.Table` :return: A Table object representing the new Amazon DynamoDB table. """ @@ -270,10 +307,10 @@ class Layer2(object): :type table: :class:`boto.dynamodb.table.Table` :param table: The Table object whose throughput is being updated. - + :type read_units: int :param read_units: The new value for ReadCapacityUnits. - + :type write_units: int :param write_units: The new value for WriteCapacityUnits. """ @@ -281,7 +318,7 @@ class Layer2(object): {'ReadCapacityUnits': read_units, 'WriteCapacityUnits': write_units}) table.update_from_response(response) - + def delete_table(self, table): """ Delete this table and all items in it. After calling this @@ -303,16 +340,18 @@ class Layer2(object): :type hash_key_proto_value: int|long|float|str|unicode :param hash_key_proto_value: A sample or prototype of the type - of value you want to use for the HashKey. - + of value you want to use for the HashKey. Alternatively, + you can also just pass in the Python type (e.g. int, float, etc.). + :type range_key_name: str :param range_key_name: The name of the RangeKey for the schema. This parameter is optional. :type range_key_proto_value: int|long|float|str|unicode :param range_key_proto_value: A sample or prototype of the type - of value you want to use for the RangeKey. This parameter - is optional. + of value you want to use for the RangeKey. Alternatively, + you can also pass in the Python type (e.g. int, float, etc.) + This parameter is optional. """ schema = {} hash_key = {} @@ -336,17 +375,17 @@ class Layer2(object): :type table: :class:`boto.dynamodb.table.Table` :param table: The Table object from which the item is retrieved. - + :type hash_key: int|long|float|str|unicode :param hash_key: The HashKey of the requested item. The type of the value must match the type defined in the schema for the table. - + :type range_key: int|long|float|str|unicode :param range_key: The optional RangeKey of the requested item. The type of the value must match the type defined in the schema for the table. - + :type attributes_to_get: list :param attributes_to_get: A list of attribute names. If supplied, only the specified attribute names will @@ -383,10 +422,24 @@ class Layer2(object): batch of objects that you wish to retrieve in this request. """ - request_items = self.dynamize_request_items(batch_list) + request_items = batch_list.to_dict() return self.layer1.batch_get_item(request_items, object_hook=item_object_hook) + def batch_write_item(self, batch_list): + """ + Performs multiple Puts and Deletes in one batch. + + :type batch_list: :class:`boto.dynamodb.batch.BatchWriteList` + :param batch_list: A BatchWriteList object which consists of a + list of :class:`boto.dynamoddb.batch.BatchWrite` objects. + Each Batch object contains the information about one + batch of objects that you wish to put or delete. + """ + request_items = batch_list.to_dict() + return self.layer1.batch_write_item(request_items, + object_hook=item_object_hook) + def put_item(self, item, expected_value=None, return_values=None): """ Store a new item or completely replace an existing item @@ -394,7 +447,7 @@ class Layer2(object): :type item: :class:`boto.dynamodb.item.Item` :param item: The Item to write to Amazon DynamoDB. - + :type expected_value: dict :param expected_value: A dictionary of name/value pairs that you expect. This dictionary should have name/value pairs where the name @@ -408,7 +461,6 @@ class Layer2(object): values are: None or 'ALL_OLD'. If 'ALL_OLD' is specified and the item is overwritten, the content of the old item is returned. - """ expected_value = self.dynamize_expected_value(expected_value) response = self.layer1.put_item(item.table.name, @@ -418,7 +470,7 @@ class Layer2(object): if 'ConsumedCapacityUnits' in response: item.consumed_units = response['ConsumedCapacityUnits'] return response - + def update_item(self, item, expected_value=None, return_values=None): """ Commit pending item updates to Amazon DynamoDB. @@ -460,21 +512,21 @@ class Layer2(object): if 'ConsumedCapacityUnits' in response: item.consumed_units = response['ConsumedCapacityUnits'] return response - + def delete_item(self, item, expected_value=None, return_values=None): """ Delete the item from Amazon DynamoDB. :type item: :class:`boto.dynamodb.item.Item` :param item: The Item to delete from Amazon DynamoDB. - + :type expected_value: dict :param expected_value: A dictionary of name/value pairs that you expect. This dictionary should have name/value pairs where the name is the name of the attribute and the value is either the value you are expecting or False if you expect the attribute not to exist. - + :type return_values: str :param return_values: Controls the return of attribute name-value pairs before then were changed. Possible @@ -497,10 +549,10 @@ class Layer2(object): item_class=Item): """ Perform a query on the table. - + :type table: :class:`boto.dynamodb.table.Table` :param table: The Table object that is being queried. - + :type hash_key: int|long|float|str|unicode :param hash_key: The HashKey of the requested item. The type of the value must match the type defined in the @@ -515,7 +567,7 @@ class Layer2(object): The only condition which expects or will accept two values is 'BETWEEN', otherwise a single value should be passed to the Condition constructor. - + :type attributes_to_get: list :param attributes_to_get: A list of attribute names. If supplied, only the specified attribute names will @@ -555,39 +607,29 @@ class Layer2(object): to generate the items. This should be a subclass of :class:`boto.dynamodb.item.Item` - :rtype: generator + :rtype: :class:`boto.dynamodb.layer2.TableGenerator` """ if range_key_condition: rkc = self.dynamize_range_key_condition(range_key_condition) else: rkc = None if exclusive_start_key: - esk = self.build_key_from_values(table.schema, *exclusive_start_key) + esk = self.build_key_from_values(table.schema, + *exclusive_start_key) else: esk = None - response = True - n = 0 - while response: - if max_results and n == max_results: - break - if response is True: - pass - elif response.has_key("LastEvaluatedKey"): - lek = response['LastEvaluatedKey'] - esk = self.dynamize_last_evaluated_key(lek) - else: - break - response = self.layer1.query(table.name, - dynamize_value(hash_key), - rkc, attributes_to_get, request_limit, - consistent_read, scan_index_forward, - esk, object_hook=item_object_hook) - for item in response['Items']: - if max_results and n == max_results: - break - yield item_class(table, attrs=item) - n += 1 - + kwargs = {'table_name': table.name, + 'hash_key_value': dynamize_value(hash_key), + 'range_key_conditions': rkc, + 'attributes_to_get': attributes_to_get, + 'limit': request_limit, + 'consistent_read': consistent_read, + 'scan_index_forward': scan_index_forward, + 'exclusive_start_key': esk, + 'object_hook': item_object_hook} + return TableGenerator(table, self.layer1.query, + max_results, item_class, kwargs) + def scan(self, table, scan_filter=None, attributes_to_get=None, request_limit=None, max_results=None, count=False, exclusive_start_key=None, item_class=Item): @@ -652,31 +694,19 @@ class Layer2(object): to generate the items. This should be a subclass of :class:`boto.dynamodb.item.Item` - :rtype: generator + :rtype: :class:`boto.dynamodb.layer2.TableGenerator` """ if exclusive_start_key: - esk = self.build_key_from_values(table.schema, *exclusive_start_key) + esk = self.build_key_from_values(table.schema, + *exclusive_start_key) else: esk = None - sf = self.dynamize_scan_filter(scan_filter) - response = True - n = 0 - while response: - if response is True: - pass - elif response.has_key("LastEvaluatedKey"): - last_evaluated_key = response['LastEvaluatedKey'] - esk = self.dynamize_item(last_evaluated_key) - else: - break - response = self.layer1.scan(table.name, sf, - attributes_to_get, request_limit, - count, esk, - object_hook=item_object_hook) - if response: - for item in response['Items']: - if max_results and n == max_results: - break - yield item_class(table, attrs=item) - n += 1 - + kwargs = {'table_name': table.name, + 'scan_filter': self.dynamize_scan_filter(scan_filter), + 'attributes_to_get': attributes_to_get, + 'limit': request_limit, + 'count': count, + 'exclusive_start_key': esk, + 'object_hook': item_object_hook} + return TableGenerator(table, self.layer1.scan, + max_results, item_class, kwargs) |