Skip to content

Commit 856e039

Browse files
committed
Add public_base_url option to S3Store
It's useful when used with CDN
1 parent 5b0f0e2 commit 856e039

File tree

3 files changed

+48
-11
lines changed

3 files changed

+48
-11
lines changed

docs/changes.rst

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ To be released.
88

99
- Added :mod:`sqlalchemy_imageattach.migration` module for storage migration.
1010
See also :ref:`migrate-store` guide.
11+
- Added ``public_base_url`` option to :class:`S3Store
12+
<sqlalchemy_imageattach.stores.s3.S3Store>`. It's useful when used with
13+
CDN e.g. CloudFront_.
14+
15+
.. _CloudFront: http://aws.amazon.com/cloudfront/
1116

1217

1318
Version 0.8.0

sqlalchemy_imageattach/stores/s3.py

+28-7
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ class S3Store(Store):
134134
:param prefix: the optional key prefix to logically separate stores
135135
with the same bucket. not used by default
136136
:type prefix: :class:`basestring`
137+
:param public_base_url: an optional url base for public urls.
138+
useful when used with cdn
139+
:type public_base_url: :class:`basestring`
140+
141+
.. versionchanged:: 0.8.1
142+
Added ``public_base_url`` parameter.
137143
138144
"""
139145

@@ -150,16 +156,25 @@ class S3Store(Store):
150156
#: stores with the same bucket.
151157
prefix = None
152158

159+
#: (:class:`basestring`) The optional url base for public urls.
160+
public_base_url = None
161+
153162
def __init__(self, bucket, access_key=None, secret_key=None,
154-
max_age=DEFAULT_MAX_AGE, prefix=''):
163+
max_age=DEFAULT_MAX_AGE, prefix='', public_base_url=None):
155164
self.bucket = bucket
156165
self.access_key = access_key
157166
self.secret_key = secret_key
158167
self.base_url = BASE_URL_FORMAT.format(bucket)
159168
self.max_age = max_age
160169
self.prefix = prefix.strip()
161170
if self.prefix.endswith('/'):
162-
self.prefix = self.prfix[:-1]
171+
self.prefix = self.prefix.rstrip('/')
172+
if public_base_url is None:
173+
self.public_base_url = self.base_url
174+
elif public_base_url.endswith('/'):
175+
self.public_base_url = public_base_url.rstrip('/')
176+
else:
177+
self.public_base_url = public_base_url
163178

164179
def get_key(self, object_type, object_id, width, height, mimetype):
165180
key = '{0}/{1}/{2}x{3}{4}'.format(
@@ -171,16 +186,22 @@ def get_key(self, object_type, object_id, width, height, mimetype):
171186
return key
172187

173188
def get_file(self, *args, **kwargs):
174-
url = self.get_url(*args, **kwargs)
189+
url = self.get_s3_url(*args, **kwargs)
175190
request = self.make_request(url)
176191
return urllib2.urlopen(request)
177192

178-
def get_url(self, *args, **kwargs):
193+
def get_s3_url(self, *args, **kwargs):
179194
return '{0}/{1}'.format(
180195
self.base_url,
181196
self.get_key(*args, **kwargs)
182197
)
183198

199+
def get_url(self, *args, **kwargs):
200+
return '{0}/{1}'.format(
201+
self.public_base_url,
202+
self.get_key(*args, **kwargs)
203+
)
204+
184205
def make_request(self, url, *args, **kwargs):
185206
return S3Request(url, *args,
186207
bucket=self.bucket,
@@ -219,11 +240,11 @@ def upload_file(self, url, data, content_type, rrs, acl='public-read'):
219240

220241
def put_file(self, file, object_type, object_id, width, height, mimetype,
221242
reproducible):
222-
url = self.get_url(object_type, object_id, width, height, mimetype)
243+
url = self.get_s3_url(object_type, object_id, width, height, mimetype)
223244
self.upload_file(url, file.read(), mimetype, rrs=reproducible)
224245

225246
def delete_file(self, *args, **kwargs):
226-
url = self.get_url(*args, **kwargs)
247+
url = self.get_s3_url(*args, **kwargs)
227248
request = self.make_request(url, method='DELETE')
228249
urllib2.urlopen(request).read()
229250

@@ -311,7 +332,7 @@ def put_file(self, *args, **kwargs):
311332
def delete_file(self, object_type, object_id, width, height, mimetype):
312333
args = object_type, object_id, width, height, mimetype
313334
self.overriding.delete_file(*args)
314-
url = self.overriding.get_url(*args)
335+
url = self.overriding.get_s3_url(*args)
315336
self.overriding.upload_file(
316337
url,
317338
data=b'',

tests/stores/s3_test.py

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import functools
2+
import itertools
23
import os.path
34
import re
45
try:
@@ -75,9 +76,15 @@ def s3_sandbox_store_getter(request):
7576
secret_key=secret_key)
7677

7778

78-
@mark.parametrize('prefix', ['', 'prefixtest'])
79-
def test_s3_store(prefix, s3_store_getter):
80-
s3 = s3_store_getter(prefix=prefix)
79+
@mark.parametrize(
80+
('prefix', 'public_base_url'),
81+
list(
82+
itertools.product(['', 'prefixtest', 'prefixtest/'],
83+
[None, 'http://example.com', 'https://example.com/'])
84+
)
85+
)
86+
def test_s3_store(prefix, public_base_url, s3_store_getter):
87+
s3 = s3_store_getter(prefix=prefix, public_base_url=public_base_url)
8188
thing_id = uuid.uuid1().int
8289
image = TestingImage(thing_id=thing_id, width=405, height=640,
8390
mimetype='image/jpeg', original=True,
@@ -90,7 +97,11 @@ def test_s3_store(prefix, s3_store_getter):
9097
with s3.open(image) as actual:
9198
actual_data = actual.read()
9299
assert expected_data == actual_data
93-
expected_url = s3.get_url('testing', thing_id, 405, 640, 'image/jpeg')
100+
key_args = 'testing', thing_id, 405, 640, 'image/jpeg'
101+
if public_base_url:
102+
expected_url = public_base_url.rstrip('/') + '/' + s3.get_key(*key_args)
103+
else:
104+
expected_url = s3.get_url(*key_args)
94105
actual_url = s3.locate(image)
95106
assert remove_query(expected_url) == remove_query(actual_url)
96107
if prefix:

0 commit comments

Comments
 (0)