Permalink
Browse files

Expose send_file max-age as config value, #433.

Need to add the same hook in a Blueprint, but this is the first such
case where we need app.config in the Blueprint.
  • Loading branch information...
rduplain committed Mar 13, 2012
1 parent 146088d commit d94efc6db63516b7f72e58c34ae33700f3d9c4fb
Showing with 55 additions and 14 deletions.
  1. +8 −4 CHANGES
  2. +8 −1 docs/config.rst
  3. +7 −0 flask/app.py
  4. +9 −5 flask/helpers.py
  5. +14 −0 flask/testsuite/blueprints.py
  6. +9 −4 flask/testsuite/helpers.py
View
12 CHANGES
@@ -48,10 +48,14 @@ Relase date to be decided, codename to be chosen.
- View functions can now return a tuple with the first instance being an
instance of :class:`flask.Response`. This allows for returning
``jsonify(error="error msg"), 400`` from a view function.
-- :class:`flask.Flask` now provides a `get_static_file_options` hook for
- subclasses to override behavior of serving static files through Flask,
- optionally by filename, which for example allows changing cache controls by
- file extension.
+- :class:`flask.Flask` now provides a `get_send_file_options` hook for
+ subclasses to override behavior of serving static files from Flask when using
+ :meth:`flask.Flask.send_static_file` based on keywords in
+ :func:`flask.helpers.send_file`. This hook is provided a filename, which for
+ example allows changing cache controls by file extension. The default
+ max-age for `send_static_file` can be configured through a new
+ ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable, regardless of whether
+ the `get_send_file_options` hook is used.
Version 0.8.1
View
@@ -107,6 +107,13 @@ The following configuration values are used internally by Flask:
reject incoming requests with a
content length greater than this by
returning a 413 status code.
+``SEND_FILE_MAX_AGE_DEFAULT``: Default cache control max age to use with
+ :meth:`flask.Flask.send_static_file`, in
+ seconds. Override this value on a per-file
+ basis using the
+ :meth:`flask.Flask.get_send_file_options` and
+ :meth:`flask.Blueprint.get_send_file_options`
+ hooks. Defaults to 43200 (12 hours).
``TRAP_HTTP_EXCEPTIONS`` If this is set to ``True`` Flask will
not execute the error handlers of HTTP
exceptions but instead treat the
@@ -267,7 +274,7 @@ configuration::
class ProductionConfig(Config):
DATABASE_URI = 'mysql://user@localhost/foo'
-
+
class DevelopmentConfig(Config):
DEBUG = True
View
@@ -249,6 +249,7 @@ class Flask(_PackageBoundObject):
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'MAX_CONTENT_LENGTH': None,
+ 'SEND_FILE_MAX_AGE_DEFAULT': 12 * 60 * 60, # 12 hours
'TRAP_BAD_REQUEST_ERRORS': False,
'TRAP_HTTP_EXCEPTIONS': False
})
@@ -1020,6 +1021,12 @@ def _register_error_handler(self, key, code_or_exception, f):
self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \
.append((code_or_exception, f))
+ def get_send_file_options(self, filename):
+ # Override: Hooks in SEND_FILE_MAX_AGE_DEFAULT config.
+ options = super(Flask, self).get_send_file_options(filename)
+ options['cache_timeout'] = self.config['SEND_FILE_MAX_AGE_DEFAULT']
+ return options
+
@setupmethod
def template_filter(self, name=None):
"""A decorator that is used to register custom template filter.
View
@@ -319,6 +319,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
guessing requires a `filename` or an `attachment_filename` to be
provided.
+ Note `get_send_file_options` in :class:`flask.Flask` hooks the
+ ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable to set the default
+ cache_timeout.
+
Please never pass filenames to this function from user sources without
checking them first. Something like this is usually sufficient to
avoid security problems::
@@ -652,22 +656,22 @@ def jinja_loader(self):
return FileSystemLoader(os.path.join(self.root_path,
self.template_folder))
- def get_static_file_options(self, filename):
+ def get_send_file_options(self, filename):
"""Provides keyword arguments to send to :func:`send_from_directory`.
This allows subclasses to change the behavior when sending files based
on the filename. For example, to set the cache timeout for .js files
to 60 seconds (note the options are keywords for :func:`send_file`)::
class MyFlask(flask.Flask):
- def get_static_file_options(self, filename):
- options = super(MyFlask, self).get_static_file_options(filename)
+ def get_send_file_options(self, filename):
+ options = super(MyFlask, self).get_send_file_options(filename)
if filename.lower().endswith('.js'):
options['cache_timeout'] = 60
options['conditional'] = True
return options
- .. versionaded:: 0.9
+ .. versionadded:: 0.9
"""
return {}
@@ -680,7 +684,7 @@ def send_static_file(self, filename):
if not self.has_static_folder:
raise RuntimeError('No static folder for this object')
return send_from_directory(self.static_folder, filename,
- **self.get_static_file_options(filename))
+ **self.get_send_file_options(filename))
def open_resource(self, resource, mode='rb'):
"""Opens a resource from the application's resource folder. To see
@@ -16,6 +16,7 @@
import warnings
from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning
from werkzeug.exceptions import NotFound
+from werkzeug.http import parse_cache_control_header
from jinja2 import TemplateNotFound
@@ -357,6 +358,19 @@ def test_templates_and_static(self):
rv = c.get('/admin/static/css/test.css')
self.assert_equal(rv.data.strip(), '/* nested file */')
+ # try/finally, in case other tests use this app for Blueprint tests.
+ max_age_default = app.config['SEND_FILE_MAX_AGE_DEFAULT']
+ try:
+ expected_max_age = 3600
+ if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == expected_max_age:
+ expected_max_age = 7200
+ app.config['SEND_FILE_MAX_AGE_DEFAULT'] = expected_max_age
+ rv = c.get('/admin/static/css/test.css')
+ cc = parse_cache_control_header(rv.headers['Cache-Control'])
+ self.assert_equal(cc.max_age, expected_max_age)
+ finally:
+ app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default
+
with app.test_request_context():
self.assert_equal(flask.url_for('admin.static', filename='test.txt'),
'/admin/static/test.txt')
View
@@ -206,15 +206,20 @@ def test_attachment(self):
def test_static_file(self):
app = flask.Flask(__name__)
- # default cache timeout is 12 hours (hard-coded)
+ # default cache timeout is 12 hours
with app.test_request_context():
rv = app.send_static_file('index.html')
cc = parse_cache_control_header(rv.headers['Cache-Control'])
self.assert_equal(cc.max_age, 12 * 60 * 60)
- # override get_static_file_options with some new values and check them
+ app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600
+ with app.test_request_context():
+ rv = app.send_static_file('index.html')
+ cc = parse_cache_control_header(rv.headers['Cache-Control'])
+ self.assert_equal(cc.max_age, 3600)
+ # override get_send_file_options with some new values and check them
class StaticFileApp(flask.Flask):
- def get_static_file_options(self, filename):
- opts = super(StaticFileApp, self).get_static_file_options(filename)
+ def get_send_file_options(self, filename):
+ opts = super(StaticFileApp, self).get_send_file_options(filename)
opts['cache_timeout'] = 10
# this test catches explicit inclusion of the conditional
# keyword arg in the guts

0 comments on commit d94efc6

Please sign in to comment.