diff options
Diffstat (limited to 'boto/dynamodb/layer2.py')
-rw-r--r-- | boto/dynamodb/layer2.py | 202 |
1 files changed, 75 insertions, 127 deletions
diff --git a/boto/dynamodb/layer2.py b/boto/dynamodb/layer2.py index 858c3998..6415fb71 100644 --- a/boto/dynamodb/layer2.py +++ b/boto/dynamodb/layer2.py @@ -26,24 +26,7 @@ from boto.dynamodb.table import Table from boto.dynamodb.schema import Schema from boto.dynamodb.item import Item from boto.dynamodb.batch import BatchList - -""" -Some utility functions to deal with mapping Amazon DynamoDB types to -Python types and vice-versa. -""" - -def is_num(n): - return isinstance(n, (int, long, float, bool)) - -def is_str(n): - return isinstance(n, basestring) - -def convert_num(s): - if '.' in s: - n = float(s) - else: - n = int(s) - return n +from boto.dynamodb.types import get_dynamodb_type, dynamize_value, convert_num def item_object_hook(dct): """ @@ -51,6 +34,8 @@ def item_object_hook(dct): This hook will transform Amazon DynamoDB JSON responses to something that maps directly to native Python types. """ + if len(dct.keys()) > 1: + return dct if 'S' in dct: return dct['S'] if 'N' in dct: @@ -83,38 +68,33 @@ class Layer2(object): d[attr_name] = {"Action": action} else: d[attr_name] = {"Action": action, - "Value": self.dynamize_value(value)} + "Value": dynamize_value(value)} return d def dynamize_item(self, item): d = {} for attr_name in item: - d[attr_name] = self.dynamize_value(item[attr_name]) + d[attr_name] = dynamize_value(item[attr_name]) return d def dynamize_range_key_condition(self, range_key_condition): """ - Convert a range_key_condition parameter into the + Convert a layer2 range_key_condition parameter into the + structure required by Layer1. + """ + return range_key_condition.to_dict() + + def dynamize_scan_filter(self, scan_filter): + """ + Convert a layer2 scan_filter parameter into the structure required by Layer1. """ d = None - if range_key_condition: + if scan_filter: d = {} - for range_value in range_key_condition: - range_condition = range_key_condition[range_value] - if range_condition == 'BETWEEN': - if isinstance(range_value, tuple): - avl = [self.dynamize_value(v) for v in range_value] - else: - msg = 'BETWEEN condition requires a tuple value' - raise TypeError(msg) - elif isinstance(range_value, tuple): - msg = 'Tuple can only be supplied with BETWEEN condition' - raise TypeError(msg) - else: - avl = [self.dynamize_value(range_value)] - d = {'AttributeValueList': avl, - 'ComparisonOperator': range_condition} + for attr_name in scan_filter: + condition = scan_filter[attr_name] + d[attr_name] = condition.to_dict() return d def dynamize_expected_value(self, expected_value): @@ -132,7 +112,7 @@ class Layer2(object): elif attr_value is False: attr_value = {'Exists': False} else: - val = self.dynamize_value(expected_value[attr_name]) + val = dynamize_value(expected_value[attr_name]) attr_value = {'Value': val} d[attr_name] = attr_value return d @@ -145,10 +125,10 @@ class Layer2(object): d = None if last_evaluated_key: hash_key = last_evaluated_key['HashKeyElement'] - d = {'HashKeyElement': self.dynamize_value(hash_key)} + d = {'HashKeyElement': dynamize_value(hash_key)} if 'RangeKeyElement' in last_evaluated_key: range_key = last_evaluated_key['RangeKeyElement'] - d['RangeKeyElement'] = self.dynamize_value(range_key) + d['RangeKeyElement'] = dynamize_value(range_key) return d def dynamize_request_items(self, batch_list): @@ -177,53 +157,6 @@ class Layer2(object): d[batch.table.name] = batch_dict return d - def get_dynamodb_type(self, val): - """ - Take a scalar Python value and return a string representing - the corresponding Amazon DynamoDB type. If the value passed in is - not a supported type, raise a TypeError. - """ - if is_num(val): - dynamodb_type = 'N' - elif is_str(val): - dynamodb_type = 'S' - elif isinstance(val, (set, frozenset)): - if False not in map(is_num, val): - dynamodb_type = 'NS' - elif False not in map(is_str, val): - dynamodb_type = 'SS' - else: - raise TypeError('Unsupported type "%s" for value "%s"' % (type(val), val)) - return dynamodb_type - - def dynamize_value(self, val): - """ - Take a scalar Python value and return a dict consisting - of the Amazon DynamoDB type specification and the value that - needs to be sent to Amazon DynamoDB. If the type of the value - is not supported, raise a TypeError - """ - def _str(val): - """ - DynamoDB stores booleans as numbers. True is 1, False is 0. - This function converts Python booleans into DynamoDB friendly - representation. - """ - if isinstance(val, bool): - return str(int(val)) - return str(val) - - dynamodb_type = self.get_dynamodb_type(val) - if dynamodb_type == 'N': - val = {dynamodb_type : _str(val)} - elif dynamodb_type == 'S': - val = {dynamodb_type : val} - elif dynamodb_type == 'NS': - val = {dynamodb_type : [ str(n) for n in val]} - elif dynamodb_type == 'SS': - val = {dynamodb_type : [ n for n in val]} - return val - def build_key_from_values(self, schema, hash_key, range_key=None): """ Build a Key structure to be used for accessing items @@ -245,13 +178,13 @@ class Layer2(object): type defined in the schema. """ dynamodb_key = {} - dynamodb_value = self.dynamize_value(hash_key) + dynamodb_value = dynamize_value(hash_key) if dynamodb_value.keys()[0] != schema.hash_key_type: msg = 'Hashkey must be of type: %s' % schema.hash_key_type raise TypeError(msg) dynamodb_key['HashKeyElement'] = dynamodb_value - if range_key: - dynamodb_value = self.dynamize_value(range_key) + if range_key is not None: + dynamodb_value = dynamize_value(range_key) if dynamodb_value.keys()[0] != schema.range_key_type: msg = 'RangeKey must be of type: %s' % schema.range_key_type raise TypeError(msg) @@ -265,24 +198,22 @@ class Layer2(object): """ return BatchList(self) - def list_tables(self, limit=None, start_table=None): + def list_tables(self, limit=None): """ - Return a list of the names of all Tables associated with the + Return a list of the names of all tables associated with the current account and region. - TODO - Layer2 should probably automatically handle pagination. :type limit: int :param limit: The maximum number of tables to return. - - :type start_table: str - :param limit: The name of the table that starts the - list. If you ran a previous list_tables and not - all results were returned, the response dict would - include a LastEvaluatedTableName attribute. Use - that value here to continue the listing. """ - result = self.layer1.list_tables(limit, start_table) - return result['TableNames'] + tables = [] + while True: + result = self.layer1.list_tables(limit) + tables.extend(result.get('TableNames', [])) + start_table = result.get('LastEvaluatedTableName', None) + if not start_table: + break + return tables def describe_table(self, name): """ @@ -349,7 +280,7 @@ class Layer2(object): response = self.layer1.update_table(table.name, {'ReadCapacityUnits': read_units, 'WriteCapacityUnits': write_units}) - table.update_from_response(response['TableDescription']) + table.update_from_response(response) def delete_table(self, table): """ @@ -386,13 +317,13 @@ class Layer2(object): schema = {} hash_key = {} hash_key['AttributeName'] = hash_key_name - hash_key_type = self.get_dynamodb_type(hash_key_proto_value) + hash_key_type = get_dynamodb_type(hash_key_proto_value) hash_key['AttributeType'] = hash_key_type schema['HashKeyElement'] = hash_key if range_key_name and range_key_proto_value is not None: range_key = {} range_key['AttributeName'] = range_key_name - range_key_type = self.get_dynamodb_type(range_key_proto_value) + range_key_type = get_dynamodb_type(range_key_proto_value) range_key['AttributeType'] = range_key_type schema['RangeKeyElement'] = range_key return Schema(schema) @@ -575,18 +506,15 @@ class Layer2(object): type of the value must match the type defined in the schema for the table. - :type range_key_condition: dict - :param range_key_condition: A dict where the key is either - a scalar value appropriate for the RangeKey in the schema - of the database or a tuple of such values. The value - associated with this key in the dict will be one of the - following conditions: + :type range_key_condition: :class:`boto.dynamodb.condition.Condition` + :param range_key_condition: A Condition object. + Condition object can be one of the following types: - 'EQ'|'LE'|'LT'|'GE'|'GT'|'BEGINS_WITH'|'BETWEEN' + EQ|LE|LT|GE|GT|BEGINS_WITH|BETWEEN - The only condition which expects or will accept a tuple - of values is 'BETWEEN', otherwise a scalar value should - be used as the key in the dict. + 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. @@ -629,10 +557,15 @@ class Layer2(object): :rtype: generator """ - rkc = self.dynamize_range_key_condition(range_key_condition) + if range_key_condition: + rkc = self.dynamize_range_key_condition(range_key_condition) + else: + rkc = 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"): @@ -641,7 +574,7 @@ class Layer2(object): else: break response = self.layer1.query(table.name, - self.dynamize_value(hash_key), + dynamize_value(hash_key), rkc, attributes_to_get, request_limit, consistent_read, scan_index_forward, exclusive_start_key, @@ -656,16 +589,30 @@ class Layer2(object): attributes_to_get=None, request_limit=None, max_results=None, count=False, exclusive_start_key=None, item_class=Item): """ - Perform a scan of DynamoDB. This version is currently punting - and expecting you to provide a full and correct JSON body - which is passed as is to DynamoDB. + Perform a scan of DynamoDB. - :type table: Table - :param table: The table to scan from - - :type scan_filter: dict - :param scan_filter: A Python version of the - ScanFilter data structure. + :type table: :class:`boto.dynamodb.table.Table` + :param table: The Table object that is being scanned. + + :type scan_filter: A dict + :param scan_filter: A dictionary where the key is the + attribute name and the value is a + :class:`boto.dynamodb.condition.Condition` object. + Valid Condition objects include: + + * EQ - equal (1) + * NE - not equal (1) + * LE - less than or equal (1) + * LT - less than (1) + * GE - greater than or equal (1) + * GT - greater than (1) + * NOT_NULL - attribute exists (0, use None) + * NULL - attribute does not exist (0, use None) + * CONTAINS - substring or value in list (1) + * NOT_CONTAINS - absence of substring or value in list (1) + * BEGINS_WITH - substring prefix (1) + * IN - exact match in list (N) + * BETWEEN - >= first value, <= second value (2) :type attributes_to_get: list :param attributes_to_get: A list of attribute names. @@ -704,6 +651,7 @@ class Layer2(object): :rtype: generator """ + sf = self.dynamize_scan_filter(scan_filter) response = True n = 0 while response: @@ -714,7 +662,7 @@ class Layer2(object): else: break - response = self.layer1.scan(table.name, scan_filter, + response = self.layer1.scan(table.name, sf, attributes_to_get,request_limit, count, exclusive_start_key, object_hook=item_object_hook) |