Merge pull request #48 from spulec/py26

Now supporting Python 2.6 as long as we're using the very latest HTTPretty
This commit is contained in:
Steve Pulec 2013-10-03 17:43:37 -07:00
commit d8ca75deaa
24 changed files with 85 additions and 64 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
moto.egg-info/* moto.egg-info/*
dist/* dist/*
.tox
.coverage .coverage
*.pyc *.pyc

View File

@ -1,5 +1,6 @@
language: python language: python
python: python:
- 2.6
- 2.7 - 2.7
env: env:
matrix: matrix:
@ -12,6 +13,7 @@ env:
- BOTO_VERSION=2.7 - BOTO_VERSION=2.7
install: install:
- pip install boto==$BOTO_VERSION - pip install boto==$BOTO_VERSION
- pip install https://github.com/gabrielfalcao/HTTPretty/tarball/8bbbdfc14326678b1aeba6a2d81af0d835a2cd6f
- pip install . - pip install .
- pip install -r requirements.txt - pip install -r requirements.txt
script: script:

View File

@ -46,7 +46,7 @@ class BaseResponse(object):
status = new_headers.pop('status', 200) status = new_headers.pop('status', 200)
headers.update(new_headers) headers.update(new_headers)
return status, headers, body 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): def metadata_response(request, full_url, headers):

View File

@ -31,7 +31,7 @@ def get_random_hex(length=8):
def get_random_message_id(): 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): 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__ outer = self.callback.im_class.__name__
else: else:
outer = self.callback.__module__ outer = self.callback.__module__
return "{}.{}".format(outer, self.callback.__name__) return "{0}.{1}".format(outer, self.callback.__name__)
def __call__(self, args=None, **kwargs): def __call__(self, args=None, **kwargs):
headers = dict(request.headers) headers = dict(request.headers)

View File

@ -1,7 +1,14 @@
from collections import defaultdict, OrderedDict from collections import defaultdict
import datetime import datetime
import json 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 moto.core import BaseBackend
from .comparisons import get_comparison_func from .comparisons import get_comparison_func
from .utils import unix_time from .utils import unix_time
@ -36,7 +43,7 @@ class DynamoType(object):
) )
def __repr__(self): def __repr__(self):
return "DynamoType: {}".format(self.to_json()) return "DynamoType: {0}".format(self.to_json())
def to_json(self): def to_json(self):
return {self.type: self.value} return {self.type: self.value}
@ -62,7 +69,7 @@ class Item(object):
self.attrs[key] = DynamoType(value) self.attrs[key] = DynamoType(value)
def __repr__(self): def __repr__(self):
return "Item: {}".format(self.to_json()) return "Item: {0}".format(self.to_json())
def to_json(self): def to_json(self):
attributes = {} attributes = {}

View File

@ -1,7 +1,5 @@
import datetime import calendar
def unix_time(dt): def unix_time(dt):
epoch = datetime.datetime.utcfromtimestamp(0) return calendar.timegm(dt.timetuple())
delta = dt - epoch
return delta.total_seconds()

View File

@ -280,7 +280,7 @@ class SecurityRule(object):
@property @property
def unique_representation(self): def unique_representation(self):
return "{}-{}-{}-{}-{}".format( return "{0}-{1}-{2}-{3}-{4}".format(
self.ip_protocol, self.ip_protocol,
self.from_port, self.from_port,
self.to_port, self.to_port,

View File

@ -12,7 +12,7 @@ class AmisResponse(object):
instance_id = instance_ids[0] instance_id = instance_ids[0]
image = ec2_backend.create_image(instance_id, name, description) image = ec2_backend.create_image(instance_id, name, description)
if not image: 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) template = Template(CREATE_IMAGE_RESPONSE)
return template.render(image=image) return template.render(image=image)

View File

@ -39,7 +39,7 @@ class ElasticBlockStore(object):
success = ec2_backend.delete_snapshot(snapshot_id) success = ec2_backend.delete_snapshot(snapshot_id)
if not success: if not success:
# Snapshot doesn't exist # 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 return DELETE_SNAPSHOT_RESPONSE
def delete_volume(self): def delete_volume(self):
@ -47,7 +47,7 @@ class ElasticBlockStore(object):
success = ec2_backend.delete_volume(volume_id) success = ec2_backend.delete_volume(volume_id)
if not success: if not success:
# Volume doesn't exist # 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 return DELETE_VOLUME_RESPONSE
def describe_snapshot_attribute(self): def describe_snapshot_attribute(self):
@ -77,7 +77,7 @@ class ElasticBlockStore(object):
attachment = ec2_backend.detach_volume(volume_id, instance_id, device_path) attachment = ec2_backend.detach_volume(volume_id, instance_id, device_path)
if not attachment: if not attachment:
# Volume wasn't attached # 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) template = Template(DETATCH_VOLUME_RESPONSE)
return template.render(attachment=attachment) return template.render(attachment=attachment)

View File

@ -34,7 +34,7 @@ class SecurityGroups(object):
group = ec2_backend.create_security_group(name, description) group = ec2_backend.create_security_group(name, description)
if not group: if not group:
# There was an exisitng 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) template = Template(CREATE_SECURITY_GROUP_RESPONSE)
return template.render(group=group) return template.render(group=group)
@ -45,7 +45,7 @@ class SecurityGroups(object):
if not group: if not group:
# There was no such 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 return DELETE_GROUP_RESPONSE
def describe_security_groups(self): def describe_security_groups(self):

View File

@ -7,7 +7,7 @@ def random_id(prefix=''):
chars = range(10) + ['a', 'b', 'c', 'd', 'e', 'f'] chars = range(10) + ['a', 'b', 'c', 'd', 'e', 'f']
instance_tag = ''.join(unicode(random.choice(chars)) for x in range(size)) 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(): def random_ami_id():
@ -60,9 +60,9 @@ def resource_ids_from_querystring(querystring_dict):
for key, value in querystring_dict.iteritems(): for key, value in querystring_dict.iteritems():
if key.startswith(prefix): if key.startswith(prefix):
resource_index = key.replace(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: if tag_value_key in querystring_dict:
tag_value = querystring_dict.get(tag_value_key)[0] tag_value = querystring_dict.get(tag_value_key)[0]
else: else:
@ -78,7 +78,7 @@ def filters_from_querystring(querystring_dict):
match = re.search("Filter.(\d).Name", key) match = re.search("Filter.(\d).Name", key)
if match: if match:
filter_index = match.groups()[0] 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)] 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 response_values[value[0]] = filter_values
return response_values return response_values

View File

@ -16,11 +16,11 @@ class ELBResponse(BaseResponse):
port_index = 1 port_index = 1
while True: while True:
try: try:
protocol = self.querystring['Listeners.member.{}.Protocol'.format(port_index)][0] protocol = self.querystring['Listeners.member.{0}.Protocol'.format(port_index)][0]
except KeyError: except KeyError:
break break
lb_port = self.querystring['Listeners.member.{}.LoadBalancerPort'.format(port_index)][0] lb_port = self.querystring['Listeners.member.{0}.LoadBalancerPort'.format(port_index)][0]
instance_port = self.querystring['Listeners.member.{}.InstancePort'.format(port_index)][0] instance_port = self.querystring['Listeners.member.{0}.InstancePort'.format(port_index)][0]
ports.append([protocol, lb_port, instance_port]) ports.append([protocol, lb_port, instance_port])
port_index += 1 port_index += 1
elb_backend.create_load_balancer( elb_backend.create_load_balancer(

View File

@ -41,7 +41,7 @@ class FakeStep(object):
arg_index = 1 arg_index = 1
while True: 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: if arg:
self.args.append(arg) self.args.append(arg)
arg_index += 1 arg_index += 1

View File

@ -14,23 +14,21 @@ class ElasticMapReduceResponse(BaseResponse):
return [value[0] for key, value in self.querystring.items() if key.startswith(param_prefix)] return [value[0] for key, value in self.querystring.items() if key.startswith(param_prefix)]
def _get_dict_param(self, param_prefix): def _get_dict_param(self, param_prefix):
return { params = {}
camelcase_to_underscores(key.replace(param_prefix, "")): value[0] for key, value in self.querystring.items():
for key, value if key.startswith(param_prefix):
in self.querystring.items() params[camelcase_to_underscores(key.replace(param_prefix, ""))] = value[0]
if key.startswith(param_prefix) return params
}
def _get_list_prefix(self, param_prefix): def _get_list_prefix(self, param_prefix):
results = [] results = []
param_index = 1 param_index = 1
while True: while True:
index_prefix = "{}.{}.".format(param_prefix, param_index) index_prefix = "{0}.{1}.".format(param_prefix, param_index)
new_items = { new_items = {}
camelcase_to_underscores(key.replace(index_prefix, "")): value[0] for key, value in self.querystring.items():
for key, value in self.querystring.items() if key.startswith(index_prefix):
if key.startswith(index_prefix) new_items[camelcase_to_underscores(key.replace(index_prefix, ""))] = value[0]
}
if not new_items: if not new_items:
break break
results.append(new_items) results.append(new_items)

View File

@ -5,10 +5,10 @@ import string
def random_job_id(size=13): def random_job_id(size=13):
chars = range(10) + list(string.uppercase) chars = range(10) + list(string.uppercase)
job_tag = ''.join(unicode(random.choice(chars)) for x in range(size)) 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): def random_instance_group_id(size=13):
chars = range(10) + list(string.uppercase) chars = range(10) + list(string.uppercase)
job_tag = ''.join(unicode(random.choice(chars)) for x in range(size)) job_tag = ''.join(unicode(random.choice(chars)) for x in range(size))
return 'i-{}'.format(job_tag) return 'i-{0}'.format(job_tag)

View File

@ -116,7 +116,7 @@ class S3Backend(BaseBackend):
if delimiter and delimiter in key_without_prefix: if delimiter and delimiter in key_without_prefix:
# If delimiter, we need to split out folder_results # If delimiter, we need to split out folder_results
key_without_delimiter = key_without_prefix.split(delimiter)[0] 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: else:
key_results.add(key) key_results.add(key)
else: else:

View File

@ -95,7 +95,7 @@ def _bucket_response(request, full_url, headers):
new_key.set_metadata(meta_key, metadata) new_key.set_metadata(meta_key, metadata)
return 200, headers, "" return 200, headers, ""
else: 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): def key_response(request, full_url, headers):
@ -172,7 +172,7 @@ def _key_response(request, full_url, headers):
template = Template(S3_DELETE_OBJECT_SUCCESS) template = Template(S3_DELETE_OBJECT_SUCCESS)
return 204, headers, template.render(bucket=removed_key) return 204, headers, template.render(bucket=removed_key)
else: 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 = """<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01"> S3_ALL_BUCKETS = """<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01">

View File

@ -45,7 +45,7 @@ class EmailResponse(BaseResponse):
destination = self.querystring.get('Destination.ToAddresses.member.1')[0] destination = self.querystring.get('Destination.ToAddresses.member.1')[0]
message = ses_backend.send_email(source, subject, body, destination) message = ses_backend.send_email(source, subject, body, destination)
if not message: 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) template = Template(SEND_EMAIL_RESPONSE)
return template.render(message=message) return template.render(message=message)
@ -56,7 +56,7 @@ class EmailResponse(BaseResponse):
message = ses_backend.send_raw_email(source, destination, raw_data) message = ses_backend.send_raw_email(source, destination, raw_data)
if not message: 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) template = Template(SEND_RAW_EMAIL_RESPONSE)
return template.render(message=message) return template.render(message=message)

View File

@ -7,7 +7,7 @@ def random_hex(length):
def get_random_message_id(): def get_random_message_id():
return "{}-{}-{}-{}-{}-{}-{}".format( return "{0}-{1}-{2}-{3}-{4}-{5}-{6}".format(
random_hex(16), random_hex(16),
random_hex(8), random_hex(8),
random_hex(4), random_hex(4),

View File

@ -26,7 +26,6 @@ class QueuesResponse(BaseResponse):
else: else:
return "", dict(status=404) return "", dict(status=404)
def list_queues(self): def list_queues(self):
queues = sqs_backend.list_queues() queues = sqs_backend.list_queues()
template = Template(LIST_QUEUES_RESPONSE) template = Template(LIST_QUEUES_RESPONSE)
@ -51,7 +50,7 @@ class QueueResponse(BaseResponse):
queue_name = self.path.split("/")[-1] queue_name = self.path.split("/")[-1]
queue = sqs_backend.delete_queue(queue_name) queue = sqs_backend.delete_queue(queue_name)
if not queue: 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) template = Template(DELETE_QUEUE_RESPONSE)
return template.render(queue=queue) return template.render(queue=queue)
@ -79,15 +78,15 @@ class QueueResponse(BaseResponse):
messages = [] messages = []
for index in range(1, 11): for index in range(1, 11):
# Loop through looking for messages # 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) message_body = self.querystring.get(message_key)
if not message_body: if not message_body:
# Found all messages # Found all messages
break 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] 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] delay_seconds = self.querystring.get(delay_key, [None])[0]
message = sqs_backend.send_message(queue_name, message_body[0], delay_seconds=delay_seconds) message = sqs_backend.send_message(queue_name, message_body[0], delay_seconds=delay_seconds)
message.user_id = message_user_id message.user_id = message_user_id
@ -118,7 +117,7 @@ class QueueResponse(BaseResponse):
message_ids = [] message_ids = []
for index in range(1, 11): for index in range(1, 11):
# Loop through looking for messages # 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) receipt_handle = self.querystring.get(receipt_key)
if not receipt_handle: if not receipt_handle:
# Found all messages # Found all messages
@ -126,7 +125,7 @@ class QueueResponse(BaseResponse):
sqs_backend.delete_message(queue_name, receipt_handle[0]) 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_user_id = self.querystring.get(message_user_id_key)[0]
message_ids.append(message_user_id) message_ids.append(message_user_id)

View File

@ -2,6 +2,19 @@
from setuptools import setup, find_packages 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( setup(
name='moto', name='moto',
version='0.2.9', version='0.2.9',
@ -16,10 +29,5 @@ setup(
], ],
}, },
packages=find_packages(), packages=find_packages(),
install_requires=[ install_requires=install_requires,
"boto",
"flask",
"httpretty>=0.6.1",
"Jinja2",
],
) )

View File

@ -365,7 +365,7 @@ def test_scan():
'Body': 'http://url_to_lolcat.gif', 'Body': 'http://url_to_lolcat.gif',
'SentBy': 'User B', 'SentBy': 'User B',
'ReceivedTime': '12/9/2011 11:36:03 PM', 'ReceivedTime': '12/9/2011 11:36:03 PM',
'Ids': {1, 2, 3}, 'Ids': set([1, 2, 3]),
'PK': 7, 'PK': 7,
} }
item = table.new_item( item = table.new_item(
@ -442,7 +442,7 @@ def test_write_batch():
'Body': 'http://url_to_lolcat.gif', 'Body': 'http://url_to_lolcat.gif',
'SentBy': 'User B', 'SentBy': 'User B',
'ReceivedTime': '12/9/2011 11:36:03 PM', 'ReceivedTime': '12/9/2011 11:36:03 PM',
'Ids': {1, 2, 3}, 'Ids': set([1, 2, 3]),
'PK': 7, 'PK': 7,
}, },
)) ))
@ -489,7 +489,7 @@ def test_batch_read():
'Body': 'http://url_to_lolcat.gif', 'Body': 'http://url_to_lolcat.gif',
'SentBy': 'User B', 'SentBy': 'User B',
'ReceivedTime': '12/9/2011 11:36:03 PM', 'ReceivedTime': '12/9/2011 11:36:03 PM',
'Ids': {1, 2, 3}, 'Ids': set([1, 2, 3]),
'PK': 7, 'PK': 7,
} }
item = table.new_item( item = table.new_item(

View File

@ -282,7 +282,7 @@ def test_scan():
'Body': 'http://url_to_lolcat.gif', 'Body': 'http://url_to_lolcat.gif',
'SentBy': 'User B', 'SentBy': 'User B',
'ReceivedTime': '12/9/2011 11:36:03 PM', 'ReceivedTime': '12/9/2011 11:36:03 PM',
'Ids': {1, 2, 3}, 'Ids': set([1, 2, 3]),
'PK': 7, 'PK': 7,
} }
item = table.new_item( item = table.new_item(
@ -356,7 +356,7 @@ def test_write_batch():
'Body': 'http://url_to_lolcat.gif', 'Body': 'http://url_to_lolcat.gif',
'SentBy': 'User B', 'SentBy': 'User B',
'ReceivedTime': '12/9/2011 11:36:03 PM', 'ReceivedTime': '12/9/2011 11:36:03 PM',
'Ids': {1, 2, 3}, 'Ids': set([1, 2, 3]),
'PK': 7, 'PK': 7,
}, },
)) ))
@ -401,7 +401,7 @@ def test_batch_read():
'Body': 'http://url_to_lolcat.gif', 'Body': 'http://url_to_lolcat.gif',
'SentBy': 'User B', 'SentBy': 'User B',
'ReceivedTime': '12/9/2011 11:36:03 PM', 'ReceivedTime': '12/9/2011 11:36:03 PM',
'Ids': {1, 2, 3}, 'Ids': set([1, 2, 3]),
'PK': 7, 'PK': 7,
} }
item = table.new_item( item = table.new_item(

8
tox.ini Normal file
View File

@ -0,0 +1,8 @@
[tox]
envlist = py26, py27
[testenv]
deps = -r{toxinidir}/requirements.txt
commands =
{envpython} setup.py test
nosetests