Merge branch 'master' of https://github.com/spulec/moto into add-empty-string-validation-exception
This commit is contained in:
commit
722859748e
@ -118,10 +118,11 @@ class Item(BaseModel):
|
|||||||
def update(self, update_expression, expression_attribute_names, expression_attribute_values):
|
def update(self, update_expression, expression_attribute_names, expression_attribute_values):
|
||||||
# Update subexpressions are identifiable by the operator keyword, so split on that and
|
# Update subexpressions are identifiable by the operator keyword, so split on that and
|
||||||
# get rid of the empty leading string.
|
# 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
|
# 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)
|
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]):
|
for action, valstr in zip(parts[:-1:2], parts[1::2]):
|
||||||
|
action = action.upper()
|
||||||
values = valstr.split(',')
|
values = valstr.split(',')
|
||||||
for value in values:
|
for value in values:
|
||||||
# A Real value
|
# A Real value
|
||||||
@ -171,6 +172,12 @@ class Item(BaseModel):
|
|||||||
decimal.Decimal(existing.value) +
|
decimal.Decimal(existing.value) +
|
||||||
decimal.Decimal(new_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:
|
else:
|
||||||
# TODO: implement other data types
|
# TODO: implement other data types
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
|
@ -151,8 +151,7 @@ class DynamoHandler(BaseResponse):
|
|||||||
return 400, {'server': 'amazon.com'}, dynamo_json_dump(
|
return 400, {'server': 'amazon.com'}, dynamo_json_dump(
|
||||||
{'__type': er,
|
{'__type': er,
|
||||||
'message': ('One or more parameter values were invalid: '
|
'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
|
overwrite = 'Expected' not in self.body
|
||||||
if not overwrite:
|
if not overwrite:
|
||||||
|
@ -255,17 +255,19 @@ EC2_RUN_INSTANCES = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc
|
|||||||
<monitoring>
|
<monitoring>
|
||||||
<state>enabled</state>
|
<state>enabled</state>
|
||||||
</monitoring>
|
</monitoring>
|
||||||
{% if instance.nics %}
|
{% if instance.subnet_id %}
|
||||||
{% if instance.nics[0].subnet %}
|
<subnetId>{{ instance.subnet_id }}</subnetId>
|
||||||
|
{% elif instance.nics[0].subnet.id %}
|
||||||
<subnetId>{{ instance.nics[0].subnet.id }}</subnetId>
|
<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>
|
<vpcId>{{ instance.nics[0].subnet.vpc_id }}</vpcId>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<privateIpAddress>{{ instance.private_ip }}</privateIpAddress>
|
<privateIpAddress>{{ instance.private_ip }}</privateIpAddress>
|
||||||
{% if instance.public_ip %}
|
{% if instance.nics[0].public_ip %}
|
||||||
<ipAddress>{{ instance.public_ip }}</ipAddress>
|
<ipAddress>{{ instance.nics[0].public_ip }}</ipAddress>
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<subnetId>{{ instance.subnet_id }}</subnetId>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<sourceDestCheck>{{ instance.source_dest_check }}</sourceDestCheck>
|
<sourceDestCheck>{{ instance.source_dest_check }}</sourceDestCheck>
|
||||||
<groupSet>
|
<groupSet>
|
||||||
@ -396,16 +398,20 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns="http://ec2.amazona
|
|||||||
<monitoring>
|
<monitoring>
|
||||||
<state>disabled</state>
|
<state>disabled</state>
|
||||||
</monitoring>
|
</monitoring>
|
||||||
{% if instance.nics %}
|
{% if instance.subnet_id %}
|
||||||
{% if instance.nics[0].subnet %}
|
<subnetId>{{ instance.subnet_id }}</subnetId>
|
||||||
|
{% elif instance.nics[0].subnet.id %}
|
||||||
<subnetId>{{ instance.nics[0].subnet.id }}</subnetId>
|
<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>
|
<vpcId>{{ instance.nics[0].subnet.vpc_id }}</vpcId>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<privateIpAddress>{{ instance.private_ip }}</privateIpAddress>
|
<privateIpAddress>{{ instance.private_ip }}</privateIpAddress>
|
||||||
{% if instance.nics[0].public_ip %}
|
{% if instance.nics[0].public_ip %}
|
||||||
<ipAddress>{{ instance.nics[0].public_ip }}</ipAddress>
|
<ipAddress>{{ instance.nics[0].public_ip }}</ipAddress>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
<sourceDestCheck>{{ instance.source_dest_check }}</sourceDestCheck>
|
<sourceDestCheck>{{ instance.source_dest_check }}</sourceDestCheck>
|
||||||
<groupSet>
|
<groupSet>
|
||||||
{% for group in instance.dynamic_group_list %}
|
{% for group in instance.dynamic_group_list %}
|
||||||
|
50
moto/s3/responses.py
Normal file → Executable file
50
moto/s3/responses.py
Normal file → Executable file
@ -276,15 +276,25 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
|||||||
if prefix and isinstance(prefix, six.binary_type):
|
if prefix and isinstance(prefix, six.binary_type):
|
||||||
prefix = prefix.decode("utf-8")
|
prefix = prefix.decode("utf-8")
|
||||||
delimiter = querystring.get('delimiter', [None])[0]
|
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(
|
result_keys, result_folders = self.backend.prefix_query(
|
||||||
bucket, prefix, delimiter)
|
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)
|
template = self.response_template(S3_BUCKET_GET_RESPONSE)
|
||||||
return 200, {}, template.render(
|
return 200, {}, template.render(
|
||||||
bucket=bucket,
|
bucket=bucket,
|
||||||
prefix=prefix,
|
prefix=prefix,
|
||||||
delimiter=delimiter,
|
delimiter=delimiter,
|
||||||
result_keys=result_keys,
|
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):
|
def _handle_list_objects_v2(self, bucket_name, querystring):
|
||||||
@ -305,20 +315,10 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
|||||||
|
|
||||||
if continuation_token or start_after:
|
if continuation_token or start_after:
|
||||||
limit = continuation_token or start_after
|
limit = continuation_token or start_after
|
||||||
continuation_index = 0
|
result_keys = self._get_results_from_token(result_keys, limit)
|
||||||
for key in result_keys:
|
|
||||||
if key.name > limit:
|
|
||||||
break
|
|
||||||
continuation_index += 1
|
|
||||||
result_keys = result_keys[continuation_index:]
|
|
||||||
|
|
||||||
if len(result_keys) > max_keys:
|
result_keys, is_truncated, \
|
||||||
is_truncated = 'true'
|
next_continuation_token = self._truncate_result(result_keys, max_keys)
|
||||||
result_keys = result_keys[:max_keys]
|
|
||||||
next_continuation_token = result_keys[-1].name
|
|
||||||
else:
|
|
||||||
is_truncated = 'false'
|
|
||||||
next_continuation_token = None
|
|
||||||
|
|
||||||
return template.render(
|
return template.render(
|
||||||
bucket=bucket,
|
bucket=bucket,
|
||||||
@ -333,6 +333,24 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
|||||||
start_after=None if continuation_token else start_after
|
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):
|
def _bucket_response_put(self, request, body, region_name, bucket_name, querystring, headers):
|
||||||
if not request.headers.get('Content-Length'):
|
if not request.headers.get('Content-Length'):
|
||||||
return 411, {}, "Content-Length required"
|
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/">
|
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||||
<Name>{{ bucket.name }}</Name>
|
<Name>{{ bucket.name }}</Name>
|
||||||
<Prefix>{{ prefix }}</Prefix>
|
<Prefix>{{ prefix }}</Prefix>
|
||||||
<MaxKeys>1000</MaxKeys>
|
<MaxKeys>{{ max_keys }}</MaxKeys>
|
||||||
<Delimiter>{{ delimiter }}</Delimiter>
|
<Delimiter>{{ delimiter }}</Delimiter>
|
||||||
<IsTruncated>false</IsTruncated>
|
<IsTruncated>{{ is_truncated }}</IsTruncated>
|
||||||
{% for key in result_keys %}
|
{% for key in result_keys %}
|
||||||
<Contents>
|
<Contents>
|
||||||
<Key>{{ key.name }}</Key>
|
<Key>{{ key.name }}</Key>
|
||||||
|
2
setup.py
2
setup.py
@ -24,7 +24,7 @@ extras_require = {
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='moto',
|
name='moto',
|
||||||
version='1.1.2',
|
version='1.1.4',
|
||||||
description='A library that allows your python tests to easily'
|
description='A library that allows your python tests to easily'
|
||||||
' mock out the boto library',
|
' mock out the boto library',
|
||||||
author='Steve Pulec',
|
author='Steve Pulec',
|
||||||
|
@ -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
|
@mock_dynamodb2
|
||||||
def test_update_item_add_value_does_not_exist_is_created():
|
def test_update_item_add_value_does_not_exist_is_created():
|
||||||
table = _create_table_with_range_key()
|
table = _create_table_with_range_key()
|
||||||
|
@ -378,11 +378,15 @@ def test_get_instances_filtering_by_vpc_id():
|
|||||||
reservations1.should.have.length_of(1)
|
reservations1.should.have.length_of(1)
|
||||||
reservations1[0].instances.should.have.length_of(1)
|
reservations1[0].instances.should.have.length_of(1)
|
||||||
reservations1[0].instances[0].id.should.equal(instance1.id)
|
reservations1[0].instances[0].id.should.equal(instance1.id)
|
||||||
|
reservations1[0].instances[0].vpc_id.should.equal(vpc1.id)
|
||||||
|
reservations1[0].instances[0].subnet_id.should.equal(subnet1.id)
|
||||||
|
|
||||||
reservations2 = conn.get_all_instances(filters={'vpc-id': vpc2.id})
|
reservations2 = conn.get_all_instances(filters={'vpc-id': vpc2.id})
|
||||||
reservations2.should.have.length_of(1)
|
reservations2.should.have.length_of(1)
|
||||||
reservations2[0].instances.should.have.length_of(1)
|
reservations2[0].instances.should.have.length_of(1)
|
||||||
reservations2[0].instances[0].id.should.equal(instance2.id)
|
reservations2[0].instances[0].id.should.equal(instance2.id)
|
||||||
|
reservations2[0].instances[0].vpc_id.should.equal(vpc2.id)
|
||||||
|
reservations2[0].instances[0].subnet_id.should.equal(subnet2.id)
|
||||||
|
|
||||||
|
|
||||||
@mock_ec2_deprecated
|
@mock_ec2_deprecated
|
||||||
@ -876,7 +880,6 @@ def test_run_instance_with_nic_autocreated():
|
|||||||
eni.private_ip_addresses.should.have.length_of(1)
|
eni.private_ip_addresses.should.have.length_of(1)
|
||||||
eni.private_ip_addresses[0].private_ip_address.should.equal(private_ip)
|
eni.private_ip_addresses[0].private_ip_address.should.equal(private_ip)
|
||||||
|
|
||||||
|
|
||||||
@mock_ec2_deprecated
|
@mock_ec2_deprecated
|
||||||
def test_run_instance_with_nic_preexisting():
|
def test_run_instance_with_nic_preexisting():
|
||||||
conn = boto.connect_vpc('the_key', 'the_secret')
|
conn = boto.connect_vpc('the_key', 'the_secret')
|
||||||
|
Loading…
Reference in New Issue
Block a user