From 8c0eff3f974c0e01979c8870e5a98adba1d54bf7 Mon Sep 17 00:00:00 2001 From: Hannes Ljungberg Date: Tue, 11 Jun 2013 00:52:04 +0200 Subject: [PATCH] Updates issue #7 - Brings back S3_USE_CACHE_CONTROL setting and fixes some documentation issues. --- docs/index.rst | 37 +++++++++++++++++++++++++------------ flask_s3.py | 32 ++++++++++++++++++++++---------- tests/test_flask_static.py | 8 ++++---- 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 2bf473e..a95768c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -139,6 +139,22 @@ generated by Flask-S3 will look like the following: that ``mybucketname`` is the name of your S3 bucket, and you have chosen to have assets served over HTTPS. +Setting Custom HTTP Headers +~~~~~~~~~~~~~~~~~ + +To set custom HTTP headers on the files served from S3 specify what +headers you want to use with the `S3_HEADERS` option. + +.. code-block:: python + + S3_HEADERS = { + 'Expires': 'Thu, 15 Apr 2010 20:00:00 GMT', + 'Cache-Control': 'max-age=86400', + } + +See `yahoo`_ more information on how to set good values for your headers. + +.. _yahoo: http://developer.yahoo.com/performance/rules.html#expires .. _settings: .. _configuration: @@ -147,7 +163,7 @@ Flask-S3 Options ---------------- Within your Flask application's settings you can provide the following -settings to control the behvaiour of Flask-S3. None of the settings are +settings to control the behaviour of Flask-S3. None of the settings are required, but if not present, some will need to be provided when uploading assets to S3. @@ -181,18 +197,15 @@ uploading assets to S3. templates will always include asset locations specified by `flask.url_for`. `S3_HEADERS` Sets custom headers to be sent with each file to S3. - - .. code-block:: python - - S3_HEADERS = { - 'Expires': 'Thu, 15 Apr 2010 20:00:00 GMT', - 'Cache-Control': 'max-age=86400', - } - - See http://developer.yahoo.com/performance/rules.html#expires - for more information. - **Default:** `{}` +`S3_CACHE_CONTROL` **Deprecated**. Please use `S3_HEADERS` instead. + This sets the value of the Cache-Control header that + is set in the metadata when `S3_USE_CACHE_CONTRL` is + set to `True`. +`S3_USE_CACHE_CONTROL` **Deprecated**. Please use `S3_HEADERS` instead. + Specifies whether or not to set the metadata for the + Cache-Control headers. + **Default:** `False` =========================== =================================================== .. _debug: http://flask.pocoo.org/docs/config/#configuration-basics diff --git a/flask_s3.py b/flask_s3.py index 03c4f3a..043dbdb 100644 --- a/flask_s3.py +++ b/flask_s3.py @@ -10,6 +10,7 @@ from boto.s3.key import Key logger = logging.getLogger('flask_s3') + def url_for(endpoint, **values): """ Generates a URL to the given endpoint. @@ -27,25 +28,27 @@ def url_for(endpoint, **values): app = current_app if 'S3_BUCKET_NAME' not in app.config: raise ValueError("S3_BUCKET_NAME not found in app configuration.") - + if app.debug and not app.config['USE_S3_DEBUG']: return flask_url_for(endpoint, **values) - + if endpoint == 'static' or endpoint.endswith('.static'): scheme = 'http' if app.config['S3_USE_HTTPS']: scheme = 'https' - bucket_path = '%s.%s' % (app.config['S3_BUCKET_NAME'], + bucket_path = '%s.%s' % (app.config['S3_BUCKET_NAME'], app.config['S3_BUCKET_DOMAIN']) urls = app.url_map.bind(bucket_path, url_scheme=scheme) return urls.build(endpoint, values=values, force_external=True) return flask_url_for(endpoint, **values) + def _bp_static_url(blueprint): """ builds the absolute url path for a blueprint's static folder """ u = u'%s%s' % (blueprint.url_prefix or '', blueprint.static_url_path or '') return u + def _gather_files(app, hidden): """ Gets all files in static folders and returns in dict.""" dirs = [(unicode(app.static_folder), app.static_url_path)] @@ -53,7 +56,7 @@ def _gather_files(app, hidden): blueprints = app.blueprints.values() bp_details = lambda x: (x.static_folder, _bp_static_url(x)) dirs.extend([bp_details(x) for x in blueprints if x.static_folder]) - + valid_files = defaultdict(list) for static_folder, static_url_loc in dirs: if not os.path.isdir(static_folder): @@ -67,10 +70,12 @@ def _gather_files(app, hidden): valid_files[(static_folder, static_url_loc)].extend(files) return valid_files + def _path_to_relative_url(path): """ Converts a folder and filename into a ralative url path """ return os.path.splitdrive(path)[1].replace('\\', '/') + def _static_folder_path(static_url, static_folder, static_asset): """ Returns a path to a file based on the static folder, and not on the @@ -88,12 +93,13 @@ def _static_folder_path(static_url, static_folder, static_asset): # Now bolt the static url path and the relative asset location together return u'%s/%s' % (static_url.rstrip('/'), rel_asset.lstrip('/')) -def _write_files(app, static_url_loc, static_folder, files, bucket, + +def _write_files(app, static_url_loc, static_folder, files, bucket, ex_keys=None): """ Writes all the files inside a static folder to S3. """ for file_path in files: asset_loc = _path_to_relative_url(file_path) - key_name = _static_folder_path(static_url_loc, static_folder, + key_name = _static_folder_path(static_url_loc, static_folder, asset_loc) msg = "Uploading %s to %s as %s" % (file_path, bucket, key_name) logger.debug(msg) @@ -101,17 +107,22 @@ def _write_files(app, static_url_loc, static_folder, files, bucket, logger.debug("%s excluded from upload" % key_name) else: k = Key(bucket=bucket, name=key_name) + if (app.config['S3_USE_CACHE_CONTROL'] and + 'S3_CACHE_CONTROL' in app.config): + k.set_metadata('Cache-Control', app.config['S3_CACHE_CONTROL']) # Set custom headers - for header, value in app.config['S3_HEADERS'].items(): + for header, value in app.config['S3_HEADERS'].iteritems(): k.set_metadata(header, value) k.set_contents_from_filename(file_path) k.make_public() + def _upload_files(app, files_, bucket): for (static_folder, static_url), names in files_.iteritems(): _write_files(app, static_url, static_folder, names, bucket) -def create_all(app, user=None, password=None, bucket_name=None, + +def create_all(app, user=None, password=None, bucket_name=None, location='', include_hidden=False): """ Uploads of the static assets associated with a Flask application to @@ -207,10 +218,11 @@ class FlaskS3(object): :param app: the :class:`flask.Flask` application object. """ - defaults = [('S3_USE_HTTPS', True), - ('USE_S3', True), + defaults = [('S3_USE_HTTPS', True), + ('USE_S3', True), ('USE_S3_DEBUG', False), ('S3_BUCKET_DOMAIN', 's3.amazonaws.com'), + ('S3_USE_CACHE_CONTROL', False), ('S3_HEADERS', {})] for k, v in defaults: app.config.setdefault(k, v) diff --git a/tests/test_flask_static.py b/tests/test_flask_static.py index 1c9803a..a8db00b 100644 --- a/tests/test_flask_static.py +++ b/tests/test_flask_static.py @@ -7,6 +7,7 @@ from flask import Flask, render_template_string, Blueprint import flask_s3 from flask_s3 import FlaskS3 + class FlaskStaticTest(unittest.TestCase): def setUp(self): self.app = Flask(__name__) @@ -28,12 +29,11 @@ class FlaskStaticTest(unittest.TestCase): """ Tests configuration vars exist. """ FlaskS3(self.app) defaults = ('S3_USE_HTTPS', 'USE_S3', 'USE_S3_DEBUG', - 'S3_BUCKET_DOMAIN', 'S3_HEADERS') + 'S3_BUCKET_DOMAIN', 'S3_USE_CACHE_CONTROL', 'S3_HEADERS') for default in defaults: self.assertIn(default, self.app.config) - class UrlTests(unittest.TestCase): def setUp(self): self.app = Flask(__name__) @@ -117,10 +117,10 @@ class S3Tests(unittest.TestCase): self.app.testing = True self.app.config['S3_BUCKET_NAME'] = 'foo' self.app.config['S3_USE_CACHE_CONTROL'] = True + self.app.config['S3_CACHE_CONTROL'] = 'cache instruction' self.app.config['S3_HEADERS'] = { 'Expires': 'Thu, 31 Dec 2037 23:59:59 GMT', 'Content-Encoding': 'gzip', - 'Cache-Control': 'max-age=86400' } def test__bp_static_url(self): @@ -213,8 +213,8 @@ class S3Tests(unittest.TestCase): exclude = ['/foo/static/foo.css', '/foo/static/foo/bar.css'] # we expect foo.css to be excluded and not uploaded expected = [call(bucket=None, name=u'/foo/static/bar.css'), + call().set_metadata('Cache-Control', 'cache instruction'), call().set_metadata('Expires', 'Thu, 31 Dec 2037 23:59:59 GMT'), - call().set_metadata('Cache-Control', 'max-age=86400'), call().set_metadata('Content-Encoding', 'gzip'), call().set_contents_from_filename('/home/z/bar.css')] flask_s3._write_files(self.app, static_url_loc, static_folder, assets,