diff --git a/.gitignore b/.gitignore index 113b41964..ee30cf86b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ moto.egg-info/* dist/* +.tox .coverage *.pyc diff --git a/.travis.yml b/.travis.yml index b6026cbe9..037501a5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: python python: + - 2.6 - 2.7 env: matrix: @@ -12,6 +13,7 @@ env: - BOTO_VERSION=2.7 install: - pip install boto==$BOTO_VERSION + - pip install https://github.com/gabrielfalcao/HTTPretty/tarball/8bbbdfc14326678b1aeba6a2d81af0d835a2cd6f - pip install . - pip install -r requirements.txt script: diff --git a/moto/core/responses.py b/moto/core/responses.py index 7e896e961..3d8fa138c 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -46,7 +46,7 @@ class BaseResponse(object): status = new_headers.pop('status', 200) headers.update(new_headers) return status, headers, body - raise NotImplementedError("The {} action has not been implemented".format(action)) + raise NotImplementedError("The {0} action has not been implemented".format(action)) def metadata_response(request, full_url, headers): diff --git a/moto/core/utils.py b/moto/core/utils.py index 53418edbf..0d47478d7 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -31,7 +31,7 @@ def get_random_hex(length=8): def get_random_message_id(): - return '{}-{}-{}-{}-{}'.format(get_random_hex(8), get_random_hex(4), get_random_hex(4), get_random_hex(4), get_random_hex(12)) + return '{0}-{1}-{2}-{3}-{4}'.format(get_random_hex(8), get_random_hex(4), get_random_hex(4), get_random_hex(4), get_random_hex(12)) def convert_regex_to_flask_path(url_path): @@ -61,7 +61,7 @@ class convert_flask_to_httpretty_response(object): outer = self.callback.im_class.__name__ else: outer = self.callback.__module__ - return "{}.{}".format(outer, self.callback.__name__) + return "{0}.{1}".format(outer, self.callback.__name__) def __call__(self, args=None, **kwargs): headers = dict(request.headers) diff --git a/moto/dynamodb/models.py b/moto/dynamodb/models.py index 66612caa8..198b6dc38 100644 --- a/moto/dynamodb/models.py +++ b/moto/dynamodb/models.py @@ -1,7 +1,14 @@ -from collections import defaultdict, OrderedDict +from collections import defaultdict import datetime import json +try: + from collections import OrderedDict +except ImportError: + # python 2.6 or earlier, use backport + from ordereddict import OrderedDict + + from moto.core import BaseBackend from .comparisons import get_comparison_func from .utils import unix_time @@ -36,7 +43,7 @@ class DynamoType(object): ) def __repr__(self): - return "DynamoType: {}".format(self.to_json()) + return "DynamoType: {0}".format(self.to_json()) def to_json(self): return {self.type: self.value} @@ -62,7 +69,7 @@ class Item(object): self.attrs[key] = DynamoType(value) def __repr__(self): - return "Item: {}".format(self.to_json()) + return "Item: {0}".format(self.to_json()) def to_json(self): attributes = {} diff --git a/moto/dynamodb/utils.py b/moto/dynamodb/utils.py index e4787d105..5ca887da6 100644 --- a/moto/dynamodb/utils.py +++ b/moto/dynamodb/utils.py @@ -1,7 +1,5 @@ -import datetime +import calendar def unix_time(dt): - epoch = datetime.datetime.utcfromtimestamp(0) - delta = dt - epoch - return delta.total_seconds() + return calendar.timegm(dt.timetuple()) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 2150f2567..1bcdbe5d7 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -280,7 +280,7 @@ class SecurityRule(object): @property def unique_representation(self): - return "{}-{}-{}-{}-{}".format( + return "{0}-{1}-{2}-{3}-{4}".format( self.ip_protocol, self.from_port, self.to_port, diff --git a/moto/ec2/responses/amis.py b/moto/ec2/responses/amis.py index feddc89f1..b95fbfab6 100644 --- a/moto/ec2/responses/amis.py +++ b/moto/ec2/responses/amis.py @@ -12,7 +12,7 @@ class AmisResponse(object): instance_id = instance_ids[0] image = ec2_backend.create_image(instance_id, name, description) if not image: - return "There is not instance with id {}".format(instance_id), dict(status=404) + return "There is not instance with id {0}".format(instance_id), dict(status=404) template = Template(CREATE_IMAGE_RESPONSE) return template.render(image=image) diff --git a/moto/ec2/responses/elastic_block_store.py b/moto/ec2/responses/elastic_block_store.py index d81c61c9d..64ad65b30 100644 --- a/moto/ec2/responses/elastic_block_store.py +++ b/moto/ec2/responses/elastic_block_store.py @@ -39,7 +39,7 @@ class ElasticBlockStore(object): success = ec2_backend.delete_snapshot(snapshot_id) if not success: # Snapshot doesn't exist - return "Snapshot with id {} does not exist".format(snapshot_id), dict(status=404) + return "Snapshot with id {0} does not exist".format(snapshot_id), dict(status=404) return DELETE_SNAPSHOT_RESPONSE def delete_volume(self): @@ -47,7 +47,7 @@ class ElasticBlockStore(object): success = ec2_backend.delete_volume(volume_id) if not success: # Volume doesn't exist - return "Volume with id {} does not exist".format(volume_id), dict(status=404) + return "Volume with id {0} does not exist".format(volume_id), dict(status=404) return DELETE_VOLUME_RESPONSE def describe_snapshot_attribute(self): @@ -77,7 +77,7 @@ class ElasticBlockStore(object): attachment = ec2_backend.detach_volume(volume_id, instance_id, device_path) if not attachment: # Volume wasn't attached - return "Volume {} can not be detached from {} because it is not attached".format(volume_id, instance_id), dict(status=404) + return "Volume {0} can not be detached from {1} because it is not attached".format(volume_id, instance_id), dict(status=404) template = Template(DETATCH_VOLUME_RESPONSE) return template.render(attachment=attachment) diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py index 1b40e182f..69d32d5fb 100644 --- a/moto/ec2/responses/security_groups.py +++ b/moto/ec2/responses/security_groups.py @@ -34,7 +34,7 @@ class SecurityGroups(object): group = ec2_backend.create_security_group(name, description) if not group: # There was an exisitng group - return "There was an existing security group with name {}".format(name), dict(status=409) + return "There was an existing security group with name {0}".format(name), dict(status=409) template = Template(CREATE_SECURITY_GROUP_RESPONSE) return template.render(group=group) @@ -45,7 +45,7 @@ class SecurityGroups(object): if not group: # There was no such group - return "There was no security group with name {}".format(name), dict(status=404) + return "There was no security group with name {0}".format(name), dict(status=404) return DELETE_GROUP_RESPONSE def describe_security_groups(self): diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index a86ed64c5..2710cc46d 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -7,7 +7,7 @@ def random_id(prefix=''): chars = range(10) + ['a', 'b', 'c', 'd', 'e', 'f'] instance_tag = ''.join(unicode(random.choice(chars)) for x in range(size)) - return '{}-{}'.format(prefix, instance_tag) + return '{0}-{1}'.format(prefix, instance_tag) def random_ami_id(): @@ -60,9 +60,9 @@ def resource_ids_from_querystring(querystring_dict): for key, value in querystring_dict.iteritems(): if key.startswith(prefix): resource_index = key.replace(prefix + ".", "") - tag_key = querystring_dict.get("Tag.{}.Key".format(resource_index))[0] + tag_key = querystring_dict.get("Tag.{0}.Key".format(resource_index))[0] - tag_value_key = "Tag.{}.Value".format(resource_index) + tag_value_key = "Tag.{0}.Value".format(resource_index) if tag_value_key in querystring_dict: tag_value = querystring_dict.get(tag_value_key)[0] else: @@ -78,7 +78,7 @@ def filters_from_querystring(querystring_dict): match = re.search("Filter.(\d).Name", key) if match: filter_index = match.groups()[0] - value_prefix = "Filter.{}.Value".format(filter_index) + value_prefix = "Filter.{0}.Value".format(filter_index) filter_values = [filter_value[0] for filter_key, filter_value in querystring_dict.iteritems() if filter_key.startswith(value_prefix)] response_values[value[0]] = filter_values return response_values diff --git a/moto/elb/responses.py b/moto/elb/responses.py index 4fcf055df..e6ba6b530 100644 --- a/moto/elb/responses.py +++ b/moto/elb/responses.py @@ -16,11 +16,11 @@ class ELBResponse(BaseResponse): port_index = 1 while True: try: - protocol = self.querystring['Listeners.member.{}.Protocol'.format(port_index)][0] + protocol = self.querystring['Listeners.member.{0}.Protocol'.format(port_index)][0] except KeyError: break - lb_port = self.querystring['Listeners.member.{}.LoadBalancerPort'.format(port_index)][0] - instance_port = self.querystring['Listeners.member.{}.InstancePort'.format(port_index)][0] + lb_port = self.querystring['Listeners.member.{0}.LoadBalancerPort'.format(port_index)][0] + instance_port = self.querystring['Listeners.member.{0}.InstancePort'.format(port_index)][0] ports.append([protocol, lb_port, instance_port]) port_index += 1 elb_backend.create_load_balancer( diff --git a/moto/emr/models.py b/moto/emr/models.py index 2fc06ef62..5dca3f62e 100644 --- a/moto/emr/models.py +++ b/moto/emr/models.py @@ -41,7 +41,7 @@ class FakeStep(object): arg_index = 1 while True: - arg = kwargs.get('hadoop_jar_step._args.member.{}'.format(arg_index)) + arg = kwargs.get('hadoop_jar_step._args.member.{0}'.format(arg_index)) if arg: self.args.append(arg) arg_index += 1 diff --git a/moto/emr/responses.py b/moto/emr/responses.py index 89da0658f..c4636ea81 100644 --- a/moto/emr/responses.py +++ b/moto/emr/responses.py @@ -14,23 +14,21 @@ class ElasticMapReduceResponse(BaseResponse): return [value[0] for key, value in self.querystring.items() if key.startswith(param_prefix)] def _get_dict_param(self, param_prefix): - return { - camelcase_to_underscores(key.replace(param_prefix, "")): value[0] - for key, value - in self.querystring.items() - if key.startswith(param_prefix) - } + params = {} + for key, value in self.querystring.items(): + if key.startswith(param_prefix): + params[camelcase_to_underscores(key.replace(param_prefix, ""))] = value[0] + return params def _get_list_prefix(self, param_prefix): results = [] param_index = 1 while True: - index_prefix = "{}.{}.".format(param_prefix, param_index) - new_items = { - camelcase_to_underscores(key.replace(index_prefix, "")): value[0] - for key, value in self.querystring.items() - if key.startswith(index_prefix) - } + index_prefix = "{0}.{1}.".format(param_prefix, param_index) + new_items = {} + for key, value in self.querystring.items(): + if key.startswith(index_prefix): + new_items[camelcase_to_underscores(key.replace(index_prefix, ""))] = value[0] if not new_items: break results.append(new_items) diff --git a/moto/emr/utils.py b/moto/emr/utils.py index 4a0d6db0e..88777fa30 100644 --- a/moto/emr/utils.py +++ b/moto/emr/utils.py @@ -5,10 +5,10 @@ import string def random_job_id(size=13): chars = range(10) + list(string.uppercase) job_tag = ''.join(unicode(random.choice(chars)) for x in range(size)) - return 'j-{}'.format(job_tag) + return 'j-{0}'.format(job_tag) def random_instance_group_id(size=13): chars = range(10) + list(string.uppercase) job_tag = ''.join(unicode(random.choice(chars)) for x in range(size)) - return 'i-{}'.format(job_tag) + return 'i-{0}'.format(job_tag) diff --git a/moto/s3/models.py b/moto/s3/models.py index 62de695fa..6098ad21c 100644 --- a/moto/s3/models.py +++ b/moto/s3/models.py @@ -116,7 +116,7 @@ class S3Backend(BaseBackend): if delimiter and delimiter in key_without_prefix: # If delimiter, we need to split out folder_results key_without_delimiter = key_without_prefix.split(delimiter)[0] - folder_results.add("{}{}{}".format(prefix, key_without_delimiter, delimiter)) + folder_results.add("{0}{1}{2}".format(prefix, key_without_delimiter, delimiter)) else: key_results.add(key) else: diff --git a/moto/s3/responses.py b/moto/s3/responses.py index 2062ccee9..2c30bbe50 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -79,12 +79,12 @@ def _bucket_response(request, full_url, headers): for kv in request.body.split('&'): k, v = kv.split('=') form[k] = v - + key = form['key'] f = form['file'] - + new_key = s3_backend.set_key(bucket_name, key, f) - + #Metadata meta_regex = re.compile('^x-amz-meta-([a-zA-Z0-9\-_]+)$', flags=re.IGNORECASE) for form_id in form: @@ -95,7 +95,7 @@ def _bucket_response(request, full_url, headers): new_key.set_metadata(meta_key, metadata) return 200, headers, "" else: - raise NotImplementedError("Method {} has not been impelemented in the S3 backend yet".format(method)) + raise NotImplementedError("Method {0} has not been impelemented in the S3 backend yet".format(method)) def key_response(request, full_url, headers): @@ -146,7 +146,7 @@ def _key_response(request, full_url, headers): # Initial data new_key = s3_backend.set_key(bucket_name, key_name, body) request.streaming = True - + #Metadata meta_regex = re.compile('^x-amz-meta-([a-zA-Z0-9\-_]+)$', flags=re.IGNORECASE) for header in request.headers: @@ -172,7 +172,7 @@ def _key_response(request, full_url, headers): template = Template(S3_DELETE_OBJECT_SUCCESS) return 204, headers, template.render(bucket=removed_key) else: - raise NotImplementedError("Method {} has not been impelemented in the S3 backend yet".format(method)) + raise NotImplementedError("Method {0} has not been impelemented in the S3 backend yet".format(method)) S3_ALL_BUCKETS = """ diff --git a/moto/ses/responses.py b/moto/ses/responses.py index 5002f925c..0c3c717be 100644 --- a/moto/ses/responses.py +++ b/moto/ses/responses.py @@ -42,7 +42,7 @@ class EmailResponse(BaseResponse): destination = self.querystring.get('Destination.ToAddresses.member.1')[0] message = ses_backend.send_email(source, subject, body, destination) if not message: - return "Did not have authority to send from email {}".format(source), dict(status=400) + return "Did not have authority to send from email {0}".format(source), dict(status=400) template = Template(SEND_EMAIL_RESPONSE) return template.render(message=message) @@ -53,7 +53,7 @@ class EmailResponse(BaseResponse): message = ses_backend.send_raw_email(source, destination, raw_data) if not message: - return "Did not have authority to send from email {}".format(source), dict(status=400) + return "Did not have authority to send from email {0}".format(source), dict(status=400) template = Template(SEND_RAW_EMAIL_RESPONSE) return template.render(message=message) diff --git a/moto/ses/utils.py b/moto/ses/utils.py index ad6c13dcc..6501290a4 100644 --- a/moto/ses/utils.py +++ b/moto/ses/utils.py @@ -7,7 +7,7 @@ def random_hex(length): def get_random_message_id(): - return "{}-{}-{}-{}-{}-{}-{}".format( + return "{0}-{1}-{2}-{3}-{4}-{5}-{6}".format( random_hex(16), random_hex(8), random_hex(4), diff --git a/moto/sqs/responses.py b/moto/sqs/responses.py index 0f582cf85..b49796545 100644 --- a/moto/sqs/responses.py +++ b/moto/sqs/responses.py @@ -26,7 +26,6 @@ class QueuesResponse(BaseResponse): else: return "", dict(status=404) - def list_queues(self): queues = sqs_backend.list_queues() template = Template(LIST_QUEUES_RESPONSE) @@ -51,7 +50,7 @@ class QueueResponse(BaseResponse): queue_name = self.path.split("/")[-1] queue = sqs_backend.delete_queue(queue_name) if not queue: - return "A queue with name {} does not exist".format(queue_name), dict(status=404) + return "A queue with name {0} does not exist".format(queue_name), dict(status=404) template = Template(DELETE_QUEUE_RESPONSE) return template.render(queue=queue) @@ -79,15 +78,15 @@ class QueueResponse(BaseResponse): messages = [] for index in range(1, 11): # Loop through looking for messages - message_key = 'SendMessageBatchRequestEntry.{}.MessageBody'.format(index) + message_key = 'SendMessageBatchRequestEntry.{0}.MessageBody'.format(index) message_body = self.querystring.get(message_key) if not message_body: # Found all messages break - message_user_id_key = 'SendMessageBatchRequestEntry.{}.Id'.format(index) + message_user_id_key = 'SendMessageBatchRequestEntry.{0}.Id'.format(index) message_user_id = self.querystring.get(message_user_id_key)[0] - delay_key = 'SendMessageBatchRequestEntry.{}.DelaySeconds'.format(index) + delay_key = 'SendMessageBatchRequestEntry.{0}.DelaySeconds'.format(index) delay_seconds = self.querystring.get(delay_key, [None])[0] message = sqs_backend.send_message(queue_name, message_body[0], delay_seconds=delay_seconds) message.user_id = message_user_id @@ -118,7 +117,7 @@ class QueueResponse(BaseResponse): message_ids = [] for index in range(1, 11): # Loop through looking for messages - receipt_key = 'DeleteMessageBatchRequestEntry.{}.ReceiptHandle'.format(index) + receipt_key = 'DeleteMessageBatchRequestEntry.{0}.ReceiptHandle'.format(index) receipt_handle = self.querystring.get(receipt_key) if not receipt_handle: # Found all messages @@ -126,7 +125,7 @@ class QueueResponse(BaseResponse): sqs_backend.delete_message(queue_name, receipt_handle[0]) - message_user_id_key = 'DeleteMessageBatchRequestEntry.{}.Id'.format(index) + message_user_id_key = 'DeleteMessageBatchRequestEntry.{0}.Id'.format(index) message_user_id = self.querystring.get(message_user_id_key)[0] message_ids.append(message_user_id) diff --git a/setup.py b/setup.py index d5c232b40..67e22feb9 100644 --- a/setup.py +++ b/setup.py @@ -2,6 +2,19 @@ from setuptools import setup, find_packages +install_requires = [ + "boto", + "flask", + "httpretty>=0.6.1", + "Jinja2", +] + +import sys + +if sys.version_info < (2, 7): + # No buildint OrderedDict before 2.7 + install_requires.append('ordereddict') + setup( name='moto', version='0.2.9', @@ -16,10 +29,5 @@ setup( ], }, packages=find_packages(), - install_requires=[ - "boto", - "flask", - "httpretty>=0.6.1", - "Jinja2", - ], + install_requires=install_requires, ) diff --git a/tests/test_dynamodb/test_dynamodb_table_with_range_key.py b/tests/test_dynamodb/test_dynamodb_table_with_range_key.py index 12700707c..38be4e493 100644 --- a/tests/test_dynamodb/test_dynamodb_table_with_range_key.py +++ b/tests/test_dynamodb/test_dynamodb_table_with_range_key.py @@ -365,7 +365,7 @@ def test_scan(): 'Body': 'http://url_to_lolcat.gif', 'SentBy': 'User B', 'ReceivedTime': '12/9/2011 11:36:03 PM', - 'Ids': {1, 2, 3}, + 'Ids': set([1, 2, 3]), 'PK': 7, } item = table.new_item( @@ -442,7 +442,7 @@ def test_write_batch(): 'Body': 'http://url_to_lolcat.gif', 'SentBy': 'User B', 'ReceivedTime': '12/9/2011 11:36:03 PM', - 'Ids': {1, 2, 3}, + 'Ids': set([1, 2, 3]), 'PK': 7, }, )) @@ -489,7 +489,7 @@ def test_batch_read(): 'Body': 'http://url_to_lolcat.gif', 'SentBy': 'User B', 'ReceivedTime': '12/9/2011 11:36:03 PM', - 'Ids': {1, 2, 3}, + 'Ids': set([1, 2, 3]), 'PK': 7, } item = table.new_item( diff --git a/tests/test_dynamodb/test_dynamodb_table_without_range_key.py b/tests/test_dynamodb/test_dynamodb_table_without_range_key.py index 81e76f7f8..3e00fb979 100644 --- a/tests/test_dynamodb/test_dynamodb_table_without_range_key.py +++ b/tests/test_dynamodb/test_dynamodb_table_without_range_key.py @@ -282,7 +282,7 @@ def test_scan(): 'Body': 'http://url_to_lolcat.gif', 'SentBy': 'User B', 'ReceivedTime': '12/9/2011 11:36:03 PM', - 'Ids': {1, 2, 3}, + 'Ids': set([1, 2, 3]), 'PK': 7, } item = table.new_item( @@ -356,7 +356,7 @@ def test_write_batch(): 'Body': 'http://url_to_lolcat.gif', 'SentBy': 'User B', 'ReceivedTime': '12/9/2011 11:36:03 PM', - 'Ids': {1, 2, 3}, + 'Ids': set([1, 2, 3]), 'PK': 7, }, )) @@ -401,7 +401,7 @@ def test_batch_read(): 'Body': 'http://url_to_lolcat.gif', 'SentBy': 'User B', 'ReceivedTime': '12/9/2011 11:36:03 PM', - 'Ids': {1, 2, 3}, + 'Ids': set([1, 2, 3]), 'PK': 7, } item = table.new_item(