Merge pull request #31 from Memoto/master
Support for metadata in S3 and POST:ing to a bucket
This commit is contained in:
commit
c14b8cae74
@ -10,6 +10,13 @@ class FakeKey(object):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.value = value
|
self.value = value
|
||||||
self.last_modified = datetime.datetime.now()
|
self.last_modified = datetime.datetime.now()
|
||||||
|
self._metadata = {}
|
||||||
|
|
||||||
|
def set_metadata(self, key, metadata):
|
||||||
|
self._metadata[key] = metadata
|
||||||
|
|
||||||
|
def get_metadata(self, key):
|
||||||
|
return self._metadata[key]
|
||||||
|
|
||||||
def append_to_value(self, value):
|
def append_to_value(self, value):
|
||||||
self.value += value
|
self.value += value
|
||||||
@ -31,6 +38,10 @@ class FakeKey(object):
|
|||||||
# https://github.com/boto/boto/issues/466
|
# https://github.com/boto/boto/issues/466
|
||||||
RFC1123 = '%a, %d %b %Y %H:%M:%S GMT'
|
RFC1123 = '%a, %d %b %Y %H:%M:%S GMT'
|
||||||
return self.last_modified.strftime(RFC1123)
|
return self.last_modified.strftime(RFC1123)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def metadata(self):
|
||||||
|
return self._metadata
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def response_dict(self):
|
def response_dict(self):
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from urlparse import parse_qs, urlparse
|
from urlparse import parse_qs, urlparse
|
||||||
|
import re
|
||||||
|
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
|
||||||
@ -67,6 +68,32 @@ def _bucket_response(request, full_url, headers):
|
|||||||
# Tried to delete a bucket that still has keys
|
# Tried to delete a bucket that still has keys
|
||||||
template = Template(S3_DELETE_BUCKET_WITH_ITEMS_ERROR)
|
template = Template(S3_DELETE_BUCKET_WITH_ITEMS_ERROR)
|
||||||
return 409, headers, template.render(bucket=removed_bucket)
|
return 409, headers, template.render(bucket=removed_bucket)
|
||||||
|
elif method == 'POST':
|
||||||
|
#POST to bucket-url should create file from form
|
||||||
|
if hasattr(request, 'form'):
|
||||||
|
#Not HTTPretty
|
||||||
|
form = request.form
|
||||||
|
else:
|
||||||
|
#HTTPretty, build new form object
|
||||||
|
form = {}
|
||||||
|
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:
|
||||||
|
result = meta_regex.match(form_id)
|
||||||
|
if result:
|
||||||
|
meta_key = result.group(0).lower()
|
||||||
|
metadata = form[form_id]
|
||||||
|
new_key.set_metadata(meta_key, metadata)
|
||||||
|
return 200, headers, ""
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("Method {} has not been impelemented in the S3 backend yet".format(method))
|
raise NotImplementedError("Method {} has not been impelemented in the S3 backend yet".format(method))
|
||||||
|
|
||||||
@ -96,7 +123,8 @@ def _key_response(request, full_url, headers):
|
|||||||
if method == 'GET':
|
if method == 'GET':
|
||||||
key = s3_backend.get_key(bucket_name, key_name)
|
key = s3_backend.get_key(bucket_name, key_name)
|
||||||
if key:
|
if key:
|
||||||
return key.value
|
headers.update(key.metadata)
|
||||||
|
return 200, headers, key.value
|
||||||
else:
|
else:
|
||||||
return 404, headers, ""
|
return 404, headers, ""
|
||||||
if method == 'PUT':
|
if method == 'PUT':
|
||||||
@ -118,14 +146,25 @@ def _key_response(request, full_url, headers):
|
|||||||
# Initial data
|
# Initial data
|
||||||
new_key = s3_backend.set_key(bucket_name, key_name, body)
|
new_key = s3_backend.set_key(bucket_name, key_name, body)
|
||||||
request.streaming = True
|
request.streaming = True
|
||||||
|
|
||||||
|
#Metadata
|
||||||
|
meta_regex = re.compile('^x-amz-meta-([a-zA-Z0-9\-_]+)$', flags=re.IGNORECASE)
|
||||||
|
for header in request.headers:
|
||||||
|
if isinstance(header, basestring):
|
||||||
|
result = meta_regex.match(header)
|
||||||
|
if result:
|
||||||
|
meta_key = result.group(0).lower()
|
||||||
|
metadata = request.headers[header]
|
||||||
|
new_key.set_metadata(meta_key, metadata)
|
||||||
template = Template(S3_OBJECT_RESPONSE)
|
template = Template(S3_OBJECT_RESPONSE)
|
||||||
headers.update(new_key.response_dict)
|
headers.update(new_key.response_dict)
|
||||||
return 200, headers, template.render(key=new_key)
|
return 200, headers, template.render(key=new_key)
|
||||||
elif method == 'HEAD':
|
elif method == 'HEAD':
|
||||||
key = s3_backend.get_key(bucket_name, key_name)
|
key = s3_backend.get_key(bucket_name, key_name)
|
||||||
if key:
|
if key:
|
||||||
|
headers.update(key.metadata)
|
||||||
headers.update(key.response_dict)
|
headers.update(key.response_dict)
|
||||||
return 200, headers, S3_OBJECT_RESPONSE
|
return 200, headers, ""
|
||||||
else:
|
else:
|
||||||
return 404, headers, ""
|
return 404, headers, ""
|
||||||
elif method == 'DELETE':
|
elif method == 'DELETE':
|
||||||
|
@ -101,7 +101,17 @@ def test_copy_key():
|
|||||||
|
|
||||||
bucket.get_key("the-key").get_contents_as_string().should.equal("some value")
|
bucket.get_key("the-key").get_contents_as_string().should.equal("some value")
|
||||||
bucket.get_key("new-key").get_contents_as_string().should.equal("some value")
|
bucket.get_key("new-key").get_contents_as_string().should.equal("some value")
|
||||||
|
|
||||||
|
@mock_s3
|
||||||
|
def test_set_metadata():
|
||||||
|
conn = boto.connect_s3('the_key', 'the_secret')
|
||||||
|
bucket = conn.create_bucket("foobar")
|
||||||
|
key = Key(bucket)
|
||||||
|
key.key = 'the-key'
|
||||||
|
key.set_metadata('md', 'Metadatastring')
|
||||||
|
key.set_contents_from_string("Testval")
|
||||||
|
|
||||||
|
bucket.get_key('the-key').get_metadata('md').should.equal('Metadatastring')
|
||||||
|
|
||||||
@freeze_time("2012-01-01 12:00:00")
|
@freeze_time("2012-01-01 12:00:00")
|
||||||
@mock_s3
|
@mock_s3
|
||||||
@ -163,9 +173,34 @@ def test_get_all_buckets():
|
|||||||
buckets.should.have.length_of(2)
|
buckets.should.have.length_of(2)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_s3
|
||||||
|
def test_post_to_bucket():
|
||||||
|
conn = boto.connect_s3('the_key', 'the_secret')
|
||||||
|
bucket = conn.create_bucket("foobar")
|
||||||
|
|
||||||
|
requests.post("https://foobar.s3.amazonaws.com/", {
|
||||||
|
'key': 'the-key',
|
||||||
|
'file': 'nothing'
|
||||||
|
})
|
||||||
|
|
||||||
|
bucket.get_key('the-key').get_contents_as_string().should.equal('nothing')
|
||||||
|
|
||||||
|
@mock_s3
|
||||||
|
def test_post_with_metadata_to_bucket():
|
||||||
|
conn = boto.connect_s3('the_key', 'the_secret')
|
||||||
|
bucket = conn.create_bucket("foobar")
|
||||||
|
|
||||||
|
requests.post("https://foobar.s3.amazonaws.com/", {
|
||||||
|
'key': 'the-key',
|
||||||
|
'file': 'nothing',
|
||||||
|
'x-amz-meta-test': 'metadata'
|
||||||
|
})
|
||||||
|
|
||||||
|
bucket.get_key('the-key').get_metadata('test').should.equal('metadata')
|
||||||
|
|
||||||
@mock_s3
|
@mock_s3
|
||||||
def test_bucket_method_not_implemented():
|
def test_bucket_method_not_implemented():
|
||||||
requests.post.when.called_with("https://foobar.s3.amazonaws.com/").should.throw(NotImplementedError)
|
requests.patch.when.called_with("https://foobar.s3.amazonaws.com/").should.throw(NotImplementedError)
|
||||||
|
|
||||||
|
|
||||||
@mock_s3
|
@mock_s3
|
||||||
|
Loading…
Reference in New Issue
Block a user