From 9070b1bf66dc4ce19d5aa8c0917b542e170ba9b3 Mon Sep 17 00:00:00 2001 From: sorensolari Date: Sun, 27 Aug 2017 13:57:16 -0600 Subject: [PATCH 1/9] fix uppercase only issue in update_expression #1091 --- moto/dynamodb2/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index 7590ee1e1..c68acbbe4 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -118,10 +118,11 @@ class Item(BaseModel): def update(self, update_expression, expression_attribute_names, expression_attribute_values): # Update subexpressions are identifiable by the operator keyword, so split on that and # get rid of the empty leading string. - parts = [p for p in re.split(r'\b(SET|REMOVE|ADD|DELETE)\b', update_expression) if p] + parts = [p for p in re.split(r'\b(SET|REMOVE|ADD|DELETE)\b', update_expression, flags=re.I) if p] # make sure that we correctly found only operator/value pairs assert len(parts) % 2 == 0, "Mismatched operators and values in update expression: '{}'".format(update_expression) for action, valstr in zip(parts[:-1:2], parts[1::2]): + action = action.upper() values = valstr.split(',') for value in values: # A Real value From 5a034973679e77da61ace263685fb13277498af9 Mon Sep 17 00:00:00 2001 From: Jack Danger Date: Fri, 8 Sep 2017 15:07:44 -0700 Subject: [PATCH 2/9] rendering vpc_id in ec2 responses --- moto/ec2/responses/instances.py | 48 +++++++++++++++----------------- tests/test_ec2/test_instances.py | 4 +++ 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index ea70b85b1..257efc1f2 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -255,18 +255,16 @@ EC2_RUN_INSTANCES = """ limit: - break - continuation_index += 1 - result_keys = result_keys[continuation_index:] + result_keys = self._get_results_from_token(result_keys, limit) if len(result_keys) > max_keys: is_truncated = 'true' @@ -333,6 +333,15 @@ class ResponseObject(_TemplateEnvironmentMixin): start_after=None if continuation_token else start_after ) + + def _get_results_from_token(self, result_keys, token): + continuation_index = 0 + for key in result_keys: + if key.name > token: + break + continuation_index += 1 + return result_keys[continuation_index:] + def _bucket_response_put(self, request, body, region_name, bucket_name, querystring, headers): if not request.headers.get('Content-Length'): return 411, {}, "Content-Length required" From 14dec68f151bfaebd1d0eaf6189698c2da0512a8 Mon Sep 17 00:00:00 2001 From: Julien Duchesne Date: Sat, 9 Sep 2017 00:27:54 -0400 Subject: [PATCH 6/9] Remove superfluous space --- moto/s3/responses.py | 1 - 1 file changed, 1 deletion(-) diff --git a/moto/s3/responses.py b/moto/s3/responses.py index 8e54fdf8e..126971228 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -333,7 +333,6 @@ class ResponseObject(_TemplateEnvironmentMixin): start_after=None if continuation_token else start_after ) - def _get_results_from_token(self, result_keys, token): continuation_index = 0 for key in result_keys: From 83dd9559db16c2973df12a4e415500331cb0d720 Mon Sep 17 00:00:00 2001 From: Julien Duchesne Date: Fri, 8 Sep 2017 23:47:16 -0400 Subject: [PATCH 7/9] Handle "max-keys" in list-objects --- moto/s3/responses.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) mode change 100644 => 100755 moto/s3/responses.py diff --git a/moto/s3/responses.py b/moto/s3/responses.py old mode 100644 new mode 100755 index 126971228..4da888de5 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -276,6 +276,7 @@ class ResponseObject(_TemplateEnvironmentMixin): if prefix and isinstance(prefix, six.binary_type): prefix = prefix.decode("utf-8") delimiter = querystring.get('delimiter', [None])[0] + max_keys = int(querystring.get('max-keys', [1000])[0]) marker = querystring.get('marker', [None])[0] result_keys, result_folders = self.backend.prefix_query( bucket, prefix, delimiter) @@ -283,13 +284,17 @@ class ResponseObject(_TemplateEnvironmentMixin): if marker: result_keys = self._get_results_from_token(result_keys, marker) + result_keys, is_truncated, _ = self._truncate_result(result_keys, max_keys) + template = self.response_template(S3_BUCKET_GET_RESPONSE) return 200, {}, template.render( bucket=bucket, prefix=prefix, delimiter=delimiter, result_keys=result_keys, - result_folders=result_folders + result_folders=result_folders, + is_truncated=is_truncated, + max_keys=max_keys ) def _handle_list_objects_v2(self, bucket_name, querystring): @@ -312,13 +317,8 @@ class ResponseObject(_TemplateEnvironmentMixin): limit = continuation_token or start_after result_keys = self._get_results_from_token(result_keys, limit) - if len(result_keys) > max_keys: - is_truncated = 'true' - result_keys = result_keys[:max_keys] - next_continuation_token = result_keys[-1].name - else: - is_truncated = 'false' - next_continuation_token = None + result_keys, is_truncated, \ + next_continuation_token = self._truncate_result(result_keys, max_keys) return template.render( bucket=bucket, @@ -341,6 +341,16 @@ class ResponseObject(_TemplateEnvironmentMixin): continuation_index += 1 return result_keys[continuation_index:] + def _truncate_result(self, result_keys, max_keys): + if len(result_keys) > max_keys: + is_truncated = 'true' + result_keys = result_keys[:max_keys] + next_continuation_token = result_keys[-1].name + else: + is_truncated = 'false' + next_continuation_token = None + return result_keys, is_truncated, next_continuation_token + def _bucket_response_put(self, request, body, region_name, bucket_name, querystring, headers): if not request.headers.get('Content-Length'): return 411, {}, "Content-Length required" @@ -841,9 +851,9 @@ S3_BUCKET_GET_RESPONSE = """ {{ bucket.name }} {{ prefix }} - 1000 + {{ max_keys }} {{ delimiter }} - false + {{ is_truncated }} {% for key in result_keys %} {{ key.name }} From 386ac94abe8e2f97893bf8d0fd40827fdc1d9d6e Mon Sep 17 00:00:00 2001 From: Brian Rower Date: Mon, 11 Sep 2017 12:06:24 -0700 Subject: [PATCH 8/9] Allow doing an ADD update of a string set Fix test --- moto/dynamodb2/models.py | 6 ++++ .../test_dynamodb_table_with_range_key.py | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index 7590ee1e1..9bb0ee441 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -171,6 +171,12 @@ class Item(BaseModel): decimal.Decimal(existing.value) + decimal.Decimal(new_value) )}) + elif set(update_action['Value'].keys()) == set(['SS']): + existing = self.attrs.get(attribute_name, DynamoType({"SS": {}})) + new_set = set(existing.value).union(set(new_value)) + self.attrs[attribute_name] = DynamoType({ + "SS": list(new_set) + }) else: # TODO: implement other data types raise NotImplementedError( diff --git a/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py b/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py index e4a586cbb..93dc5b3ef 100644 --- a/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py +++ b/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py @@ -1306,6 +1306,36 @@ def test_update_item_add_value(): }) +@mock_dynamodb2 +def test_update_item_add_value_string_set(): + table = _create_table_with_range_key() + + table.put_item(Item={ + 'forum_name': 'the-key', + 'subject': '123', + 'string_set': set(['str1', 'str2']), + }) + + item_key = {'forum_name': 'the-key', 'subject': '123'} + table.update_item( + Key=item_key, + AttributeUpdates={ + 'string_set': { + 'Action': u'ADD', + 'Value': set(['str3']), + }, + }, + ) + + returned_item = dict((k, str(v) if isinstance(v, Decimal) else v) + for k, v in table.get_item(Key=item_key)['Item'].items()) + dict(returned_item).should.equal({ + 'string_set': set(['str1', 'str2', 'str3']), + 'forum_name': 'the-key', + 'subject': '123', + }) + + @mock_dynamodb2 def test_update_item_add_value_does_not_exist_is_created(): table = _create_table_with_range_key() From 88b4d0b271ec584419f281c6e0a07fe43b5c8dbb Mon Sep 17 00:00:00 2001 From: Jack Danger Date: Mon, 11 Sep 2017 12:34:20 -0700 Subject: [PATCH 9/9] bumping version to 1.1.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index eae16b81d..7ab174d35 100755 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ extras_require = { setup( name='moto', - version='1.1.3', + version='1.1.4', description='A library that allows your python tests to easily' ' mock out the boto library', author='Steve Pulec',