Merge branch 'master' of https://github.com/spulec/moto into add-empty-string-validation-exception

This commit is contained in:
Chris Keogh 2017-09-12 09:29:35 +12:00
commit 722859748e
7 changed files with 109 additions and 46 deletions

View file

@ -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
@ -171,6 +172,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(

View file

@ -151,8 +151,7 @@ class DynamoHandler(BaseResponse):
return 400, {'server': 'amazon.com'}, dynamo_json_dump(
{'__type': er,
'message': ('One or more parameter values were invalid: '
'An AttributeValue may not contain an empty string')
})
'An AttributeValue may not contain an empty string')})
overwrite = 'Expected' not in self.body
if not overwrite:

View file

@ -255,17 +255,19 @@ EC2_RUN_INSTANCES = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc
<monitoring>
<state>enabled</state>
</monitoring>
{% if instance.nics %}
{% if instance.nics[0].subnet %}
<subnetId>{{ instance.nics[0].subnet.id }}</subnetId>
<vpcId>{{ instance.nics[0].subnet.vpc_id }}</vpcId>
{% endif %}
<privateIpAddress>{{ instance.private_ip }}</privateIpAddress>
{% if instance.public_ip %}
<ipAddress>{{ instance.public_ip }}</ipAddress>
{% endif %}
{% else %}
{% if instance.subnet_id %}
<subnetId>{{ instance.subnet_id }}</subnetId>
{% elif instance.nics[0].subnet.id %}
<subnetId>{{ instance.nics[0].subnet.id }}</subnetId>
{% endif %}
{% if instance.vpc_id %}
<vpcId>{{ instance.vpc_id }}</vpcId>
{% elif instance.nics[0].subnet.vpc_id %}
<vpcId>{{ instance.nics[0].subnet.vpc_id }}</vpcId>
{% endif %}
<privateIpAddress>{{ instance.private_ip }}</privateIpAddress>
{% if instance.nics[0].public_ip %}
<ipAddress>{{ instance.nics[0].public_ip }}</ipAddress>
{% endif %}
<sourceDestCheck>{{ instance.source_dest_check }}</sourceDestCheck>
<groupSet>
@ -396,26 +398,30 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns="http://ec2.amazona
<monitoring>
<state>disabled</state>
</monitoring>
{% if instance.nics %}
{% if instance.nics[0].subnet %}
<subnetId>{{ instance.nics[0].subnet.id }}</subnetId>
<vpcId>{{ instance.nics[0].subnet.vpc_id }}</vpcId>
{% endif %}
<privateIpAddress>{{ instance.private_ip }}</privateIpAddress>
{% if instance.nics[0].public_ip %}
<ipAddress>{{ instance.nics[0].public_ip }}</ipAddress>
{% endif %}
{% if instance.subnet_id %}
<subnetId>{{ instance.subnet_id }}</subnetId>
{% elif instance.nics[0].subnet.id %}
<subnetId>{{ instance.nics[0].subnet.id }}</subnetId>
{% endif %}
{% if instance.vpc_id %}
<vpcId>{{ instance.vpc_id }}</vpcId>
{% elif instance.nics[0].subnet.vpc_id %}
<vpcId>{{ instance.nics[0].subnet.vpc_id }}</vpcId>
{% endif %}
<privateIpAddress>{{ instance.private_ip }}</privateIpAddress>
{% if instance.nics[0].public_ip %}
<ipAddress>{{ instance.nics[0].public_ip }}</ipAddress>
{% endif %}
<sourceDestCheck>{{ instance.source_dest_check }}</sourceDestCheck>
<groupSet>
{% for group in instance.dynamic_group_list %}
<item>
{% if group.id %}
<groupId>{{ group.id }}</groupId>
<groupName>{{ group.name }}</groupName>
{% else %}
<groupId>{{ group }}</groupId>
{% endif %}
{% if group.id %}
<groupId>{{ group.id }}</groupId>
<groupName>{{ group.name }}</groupName>
{% else %}
<groupId>{{ group }}</groupId>
{% endif %}
</item>
{% endfor %}
</groupSet>

50
moto/s3/responses.py Normal file → Executable file
View file

@ -276,15 +276,25 @@ 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)
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):
@ -305,20 +315,10 @@ class ResponseObject(_TemplateEnvironmentMixin):
if continuation_token or start_after:
limit = continuation_token or start_after
continuation_index = 0
for key in result_keys:
if key.name > 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'
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,
@ -333,6 +333,24 @@ 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 _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"
@ -833,9 +851,9 @@ S3_BUCKET_GET_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>{{ bucket.name }}</Name>
<Prefix>{{ prefix }}</Prefix>
<MaxKeys>1000</MaxKeys>
<MaxKeys>{{ max_keys }}</MaxKeys>
<Delimiter>{{ delimiter }}</Delimiter>
<IsTruncated>false</IsTruncated>
<IsTruncated>{{ is_truncated }}</IsTruncated>
{% for key in result_keys %}
<Contents>
<Key>{{ key.name }}</Key>