Date: Thu, 8 Mar 2012 16:41:39 -0500
Subject: [PATCH 036/142] On JSON requests, the JSON response should have
Content-Type: application/json and the body of the response should be a JSON
object.
---
CHANGES | 2 ++
flask/testsuite/helpers.py | 12 ++++++++
flask/wrappers.py | 56 +++++++++++++++++++++++++++++++++++---
3 files changed, 66 insertions(+), 4 deletions(-)
diff --git a/CHANGES b/CHANGES
index 8311e2e758..91a31040e3 100644
--- a/CHANGES
+++ b/CHANGES
@@ -8,6 +8,8 @@ Version 0.9
Relase date to be decided, codename to be chosen.
+- The :func:`flask.Request.on_json_loading_failed` now returns a JSON formatted
+ response by default.
- The :func:`flask.url_for` function now can generate anchors to the
generated links.
- The :func:`flask.url_for` function now can also explicitly generate
diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py
index 89c6c188fc..e48f8dc392 100644
--- a/flask/testsuite/helpers.py
+++ b/flask/testsuite/helpers.py
@@ -40,6 +40,18 @@ def return_json():
rv = c.post('/json', data='malformed', content_type='application/json')
self.assert_equal(rv.status_code, 400)
+ def test_json_bad_requests_content_type(self):
+ app = flask.Flask(__name__)
+ @app.route('/json', methods=['POST'])
+ def return_json():
+ return unicode(flask.request.json)
+ c = app.test_client()
+ rv = c.post('/json', data='malformed', content_type='application/json')
+ self.assert_equal(rv.status_code, 400)
+ self.assert_equal(rv.mimetype, 'application/json')
+ self.assert_('description' in flask.json.loads(rv.data))
+ self.assert_('' not in flask.json.loads(rv.data)['description'])
+
def test_json_body_encoding(self):
app = flask.Flask(__name__)
app.testing = True
diff --git a/flask/wrappers.py b/flask/wrappers.py
index f6ec2788a0..3df697f75d 100644
--- a/flask/wrappers.py
+++ b/flask/wrappers.py
@@ -10,7 +10,7 @@
"""
from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
-from werkzeug.exceptions import BadRequest
+from werkzeug.exceptions import BadRequest, HTTPException
from werkzeug.utils import cached_property
from .debughelpers import attach_enctype_error_multidict
@@ -18,6 +18,43 @@
from .globals import _request_ctx_stack
+class JSONHTTPException(HTTPException):
+ """A base class for HTTP exceptions with ``Content-Type:
+ application/json``.
+
+ The ``description`` attribute of this class must set to a string (*not* an
+ HTML string) which describes the error.
+
+ """
+
+ def get_body(self, environ):
+ """Overrides :meth:`werkzeug.exceptions.HTTPException.get_body` to
+ return the description of this error in JSON format instead of HTML.
+
+ """
+ return json.dumps(dict(description=self.get_description(environ)))
+
+ def get_headers(self, environ):
+ """Returns a list of headers including ``Content-Type:
+ application/json``.
+
+ """
+ return [('Content-Type', 'application/json')]
+
+
+class JSONBadRequest(JSONHTTPException, BadRequest):
+ """Represents an HTTP ``400 Bad Request`` error whose body contains an
+ error message in JSON format instead of HTML format (as in the superclass).
+
+ """
+
+ #: The description of the error which occurred as a string.
+ description = (
+ 'The browser (or proxy) sent a request that this server could not '
+ 'understand.'
+ )
+
+
class Request(RequestBase):
"""The request object used by default in Flask. Remembers the
matched endpoint and view arguments.
@@ -108,12 +145,23 @@ def json(self):
def on_json_loading_failed(self, e):
"""Called if decoding of the JSON data failed. The return value of
- this method is used by :attr:`json` when an error ocurred. The
- default implementation raises a :class:`~werkzeug.exceptions.BadRequest`.
+ this method is used by :attr:`json` when an error ocurred. The default
+ implementation raises a :class:`JSONBadRequest`, which is a subclass of
+ :class:`~werkzeug.exceptions.BadRequest` which sets the
+ ``Content-Type`` to ``application/json`` and provides a JSON-formatted
+ error description::
+
+ {"description": "The browser (or proxy) sent a request that \
+ this server could not understand."}
+
+ .. versionchanged:: 0.9
+
+ Return a :class:`JSONBadRequest` instead of a
+ :class:`~werkzeug.exceptions.BadRequest` by default.
.. versionadded:: 0.8
"""
- raise BadRequest()
+ raise JSONBadRequest()
def _load_form_data(self):
RequestBase._load_form_data(self)
From 6b9e6a5a52f22bdf6b86b76de1fc7c8e1a635a8f Mon Sep 17 00:00:00 2001
From: Kevin Burke
Date: Sun, 11 Mar 2012 20:20:32 -0700
Subject: [PATCH 037/142] add heroku/deploy options to quickstart, and add more
clear links in tutorial setup.
---
docs/quickstart.rst | 36 ++++++++++++++++++++++++++++++++++++
docs/tutorial/setup.rst | 20 ++++++++++----------
2 files changed, 46 insertions(+), 10 deletions(-)
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 9fde0c2fce..ed11316c83 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -826,3 +826,39 @@ can do it like this::
from werkzeug.contrib.fixers import LighttpdCGIRootFix
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)
+
+Share your Local Server with a Friend
+-------------------------------------
+
+`Localtunnel `_ is a neat tool you can use to
+quickly share your local Flask server to a friend.
+
+To install Localtunnel, open a terminal and run the following command::
+
+ sudo gem install localtunnel
+
+Then, with Flask running at ``http://localhost:5000``, open a new Terminal window
+and type::
+
+ localtunnel 5000
+ Port 5000 is now publicly accessible from http://54xy.localtunnel.com ...
+
+*(Get a* ``gem: command not found`` *error? Download RubyGems*
+`here `_ *.)*
+
+If you load the URL given in the localtunnel output in your browser, you
+should see your Flask app. It's actually being loaded from your own computer!
+
+Deploying to a Web Server
+-------------------------
+
+`Heroku `_ offers a free web platform to host your
+Flask app, and is the easiest way for you to put your Flask app online.
+They have excellent instructions on how to deploy your Flask app `here
+`_.
+
+Other resources for deploying Flask apps:
+
+- `Deploying Flask on ep.io `_
+- `Deploying Flask on Webfaction `_
+- `Deploying Flask on Google App Engine `_
diff --git a/docs/tutorial/setup.rst b/docs/tutorial/setup.rst
index e9e4d67982..3a8fba3387 100644
--- a/docs/tutorial/setup.rst
+++ b/docs/tutorial/setup.rst
@@ -11,7 +11,7 @@ into the module which we will be doing here. However a cleaner solution
would be to create a separate `.ini` or `.py` file and load that or import
the values from there.
-::
+In `flaskr.py`::
# all the imports
import sqlite3
@@ -26,7 +26,7 @@ the values from there.
PASSWORD = 'default'
Next we can create our actual application and initialize it with the
-config from the same file::
+config from the same file, in `flaskr.py`::
# create our little application :)
app = Flask(__name__)
@@ -37,21 +37,21 @@ string it will import it) and then look for all uppercase variables
defined there. In our case, the configuration we just wrote a few lines
of code above. You can also move that into a separate file.
-It is also a good idea to be able to load a configuration from a
-configurable file. This is what :meth:`~flask.Config.from_envvar` can
-do::
+Usually, it is a good idea to load a configuration from a configurable
+file. This is what :meth:`~flask.Config.from_envvar` can do, replacing the
+:meth:`~flask.Config.from_object` line above::
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
That way someone can set an environment variable called
-:envvar:`FLASKR_SETTINGS` to specify a config file to be loaded which will
-then override the default values. The silent switch just tells Flask to
-not complain if no such environment key is set.
+:envvar:`FLASKR_SETTINGS` to specify a config file to be loaded which will then
+override the default values. The silent switch just tells Flask to not complain
+if no such environment key is set.
The `secret_key` is needed to keep the client-side sessions secure.
Choose that key wisely and as hard to guess and complex as possible. The
-debug flag enables or disables the interactive debugger. Never leave
-debug mode activated in a production system because it will allow users to
+debug flag enables or disables the interactive debugger. *Never leave
+debug mode activated in a production system*, because it will allow users to
execute code on the server!
We also add a method to easily connect to the database specified. That
From 605d0ee34421fbc1dd714790ff016834c9b3f0cb Mon Sep 17 00:00:00 2001
From: Kevin Burke
Date: Sun, 11 Mar 2012 20:33:43 -0700
Subject: [PATCH 038/142] update links
---
docs/quickstart.rst | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index ed11316c83..f1771503a6 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -852,12 +852,13 @@ should see your Flask app. It's actually being loaded from your own computer!
Deploying to a Web Server
-------------------------
-`Heroku `_ offers a free web platform to host your
-Flask app, and is the easiest way for you to put your Flask app online.
-They have excellent instructions on how to deploy your Flask app `here
-`_.
+If you want to make your Flask app available to the Internet at large, `Heroku
+`_ is very easy to set up and will run small Flask
+applications for free. `Check out their tutorial on how to deploy Flask apps on
+their service `_.
-Other resources for deploying Flask apps:
+There are a number of other websites that will host your Flask app and make it
+easy for you to do so.
- `Deploying Flask on ep.io `_
- `Deploying Flask on Webfaction `_
From 075b6b11c8b1690d946b8839e6dc4eb8a8cb7e3c Mon Sep 17 00:00:00 2001
From: James Saryerwinnie
Date: Sun, 11 Mar 2012 20:45:58 -0700
Subject: [PATCH 039/142] Fix issue 140
This allows for a view function to return something like:
jsonify(error="error msg"), 400
---
CHANGES | 3 +++
flask/app.py | 16 +++++++++++++++-
flask/testsuite/basic.py | 29 +++++++++++++++++++++++++++++
3 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/CHANGES b/CHANGES
index 8311e2e758..dbf447db87 100644
--- a/CHANGES
+++ b/CHANGES
@@ -45,6 +45,9 @@ Relase date to be decided, codename to be chosen.
- The :meth:`flask.render_template` method now accepts a either an iterable of
template names or a single template name. Previously, it only accepted a
single template name. On an iterable, the first template found is rendered.
+- 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.
Version 0.8.1
diff --git a/flask/app.py b/flask/app.py
index 15e432dee7..f3d7efcb0c 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -1361,7 +1361,21 @@ def make_response(self, rv):
if isinstance(rv, basestring):
return self.response_class(rv)
if isinstance(rv, tuple):
- return self.response_class(*rv)
+ if len(rv) > 0 and isinstance(rv[0], self.response_class):
+ original = rv[0]
+ new_response = self.response_class('', *rv[1:])
+ if len(rv) < 3:
+ # The args for the response class are
+ # response=None, status=None, headers=None,
+ # mimetype=None, content_type=None, ...
+ # so if there's at least 3 elements the rv
+ # tuple contains header information so the
+ # headers from rv[0] "win."
+ new_response.headers = original.headers
+ new_response.response = original.response
+ return new_response
+ else:
+ return self.response_class(*rv)
return self.response_class.force_type(rv, request.environ)
def create_url_adapter(self, request):
diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py
index e6a278e53a..41efb19617 100644
--- a/flask/testsuite/basic.py
+++ b/flask/testsuite/basic.py
@@ -659,6 +659,35 @@ def test_make_response(self):
self.assert_equal(rv.data, 'W00t')
self.assert_equal(rv.mimetype, 'text/html')
+ def test_make_response_with_response_instance(self):
+ app = flask.Flask(__name__)
+ with app.test_request_context():
+ rv = flask.make_response(
+ flask.jsonify({'msg': 'W00t'}), 400)
+ self.assertEqual(rv.status_code, 400)
+ self.assertEqual(rv.data,
+ '{\n "msg": "W00t"\n}')
+ self.assertEqual(rv.mimetype, 'application/json')
+
+ rv = flask.make_response(
+ flask.Response(''), 400)
+ self.assertEqual(rv.status_code, 400)
+ self.assertEqual(rv.data, '')
+ self.assertEqual(rv.mimetype, 'text/html')
+
+ rv = flask.make_response(
+ flask.Response('', headers={'Content-Type': 'text/html'}),
+ 400, None, 'application/json')
+ self.assertEqual(rv.status_code, 400)
+ self.assertEqual(rv.headers['Content-Type'], 'application/json')
+
+ rv = flask.make_response(
+ flask.Response('', mimetype='application/json'),
+ 400, {'Content-Type': 'text/html'})
+ self.assertEqual(rv.status_code, 400)
+ self.assertEqual(rv.headers['Content-Type'], 'text/html')
+
+
def test_url_generation(self):
app = flask.Flask(__name__)
@app.route('/hello/', methods=['POST'])
From 2befab24c50a8f1a3417a7ce22e65608ba3905e1 Mon Sep 17 00:00:00 2001
From: Kevin Burke
Date: Sun, 11 Mar 2012 23:17:40 -0700
Subject: [PATCH 040/142] remove localtunnel things that were added to snippets
---
docs/quickstart.rst | 23 +----------------------
1 file changed, 1 insertion(+), 22 deletions(-)
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index f1771503a6..de62e54605 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -827,28 +827,6 @@ can do it like this::
from werkzeug.contrib.fixers import LighttpdCGIRootFix
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)
-Share your Local Server with a Friend
--------------------------------------
-
-`Localtunnel `_ is a neat tool you can use to
-quickly share your local Flask server to a friend.
-
-To install Localtunnel, open a terminal and run the following command::
-
- sudo gem install localtunnel
-
-Then, with Flask running at ``http://localhost:5000``, open a new Terminal window
-and type::
-
- localtunnel 5000
- Port 5000 is now publicly accessible from http://54xy.localtunnel.com ...
-
-*(Get a* ``gem: command not found`` *error? Download RubyGems*
-`here `_ *.)*
-
-If you load the URL given in the localtunnel output in your browser, you
-should see your Flask app. It's actually being loaded from your own computer!
-
Deploying to a Web Server
-------------------------
@@ -863,3 +841,4 @@ easy for you to do so.
- `Deploying Flask on ep.io `_
- `Deploying Flask on Webfaction `_
- `Deploying Flask on Google App Engine `_
+- `Sharing your Localhost Server with Localtunnel `_
From 06b224676d2f6c38fbf1f486f636e81a85016d45 Mon Sep 17 00:00:00 2001
From: Dave Shawley
Date: Mon, 12 Mar 2012 11:19:17 -0400
Subject: [PATCH 041/142] Added _PackageBoundObject.get_static_file_options.
This method receives the name of a static file that is going to be served
up and generates a dict of options to use when serving the file. The
default set is empty so code will fall back to the existing behavior if
the method is not overridden.
I needed this method to adjust the cache control headers for .js files that
one of my applications was statically serving. The default expiration is
buried in an argument to send_file and is set to 12 hours. There was no
good way to adjust this value previously.
---
flask/helpers.py | 11 +++++++++--
flask/testsuite/helpers.py | 26 +++++++++++++++++++++++++-
2 files changed, 34 insertions(+), 3 deletions(-)
diff --git a/flask/helpers.py b/flask/helpers.py
index 25250d26c4..4cb918d2f3 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -495,7 +495,8 @@ def download_file(filename):
filename = safe_join(directory, filename)
if not os.path.isfile(filename):
raise NotFound()
- return send_file(filename, conditional=True, **options)
+ options.setdefault('conditional', True)
+ return send_file(filename, **options)
def get_root_path(import_name):
@@ -651,6 +652,11 @@ def jinja_loader(self):
return FileSystemLoader(os.path.join(self.root_path,
self.template_folder))
+ def get_static_file_options(self, filename):
+ """Function used internally to determine what keyword arguments
+ to send to :func:`send_from_directory` for a specific file."""
+ return {}
+
def send_static_file(self, filename):
"""Function used internally to send static files from the static
folder to the browser.
@@ -659,7 +665,8 @@ 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)
+ return send_from_directory(self.static_folder, filename,
+ **self.get_static_file_options(filename))
def open_resource(self, resource, mode='rb'):
"""Opens a resource from the application's resource folder. To see
diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py
index 89c6c188fc..c88026d949 100644
--- a/flask/testsuite/helpers.py
+++ b/flask/testsuite/helpers.py
@@ -17,7 +17,7 @@
from logging import StreamHandler
from StringIO import StringIO
from flask.testsuite import FlaskTestCase, catch_warnings, catch_stderr
-from werkzeug.http import parse_options_header
+from werkzeug.http import parse_cache_control_header, parse_options_header
def has_encoding(name):
@@ -204,6 +204,30 @@ def test_attachment(self):
self.assert_equal(value, 'attachment')
self.assert_equal(options['filename'], 'index.txt')
+ def test_static_file(self):
+ app = flask.Flask(__name__)
+ # default cache timeout is 12 hours (hard-coded)
+ 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
+ class StaticFileApp(flask.Flask):
+ def __init__(self):
+ super(StaticFileApp, self).__init__(__name__)
+ def get_static_file_options(self, filename):
+ opts = super(StaticFileApp, self).get_static_file_options(filename)
+ opts['cache_timeout'] = 10
+ # this test catches explicit inclusion of the conditional
+ # keyword arg in the guts
+ opts['conditional'] = True
+ return opts
+ app = StaticFileApp()
+ 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, 10)
+
class LoggingTestCase(FlaskTestCase):
From 2d237f3c533baa768b29d808f1850382542bbd2f Mon Sep 17 00:00:00 2001
From: Matt Dawson
Date: Mon, 12 Mar 2012 10:57:00 -0700
Subject: [PATCH 042/142] Fix grammar in extension dev docs.
---
docs/extensiondev.rst | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst
index 074d06ab6d..5a8d5b169b 100644
--- a/docs/extensiondev.rst
+++ b/docs/extensiondev.rst
@@ -332,9 +332,9 @@ extension to be approved you have to follow these guidelines:
2. It must ship a testing suite that can either be invoked with ``make test``
or ``python setup.py test``. For test suites invoked with ``make
test`` the extension has to ensure that all dependencies for the test
- are installed automatically, in case of ``python setup.py test``
- dependencies for tests alone can be specified in the `setup.py`
- file. The test suite also has to be part of the distribution.
+ are installed automatically. If tests are invoked with ``python setup.py
+ test``, test dependencies can be specified in the `setup.py` file. The
+ test suite also has to be part of the distribution.
3. APIs of approved extensions will be checked for the following
characteristics:
From 8216e036e96594a4932d1f6a3569fcf39fe3c2bd Mon Sep 17 00:00:00 2001
From: Thibaud Morel
Date: Mon, 12 Mar 2012 11:16:03 -0700
Subject: [PATCH 043/142] Specifying supported Python versions in setup.py
metadata
---
setup.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/setup.py b/setup.py
index 3a302cea0c..812b2c8a78 100644
--- a/setup.py
+++ b/setup.py
@@ -100,6 +100,9 @@ def run(self):
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
+ 'Programming Language :: Python :: 2.5',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Software Development :: Libraries :: Python Modules'
],
From cb24646948a8c00c5b39f0d76bf75e153ac502b1 Mon Sep 17 00:00:00 2001
From: Christoph Heer
Date: Tue, 17 Jan 2012 21:09:59 +0100
Subject: [PATCH 044/142] Add jsonp support inside of jsonify
---
flask/helpers.py | 21 +++++++++++++++++++++
flask/testsuite/helpers.py | 14 ++++++++++++++
2 files changed, 35 insertions(+)
diff --git a/flask/helpers.py b/flask/helpers.py
index 25250d26c4..ebe5fca553 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -117,9 +117,30 @@ def get_current_user():
information about this, have a look at :ref:`json-security`.
.. versionadded:: 0.2
+
+ .. versionadded:: 0.9
+ If the argument ``padded`` true than the json object will pad for
+ JSONP calls like from jquery. The response mimetype will also change
+ to ``text/javascript``.
+
+ The json object will pad as javascript function with the function name
+ from the request argument ``callback`` or ``jsonp``. If the argument
+ ``padded`` a string jsonify will look for the function name in the
+ request argument with the name which is equal to ``padded``. Is there
+ no function name it will fallback and use ``jsonp`` as function name.
"""
if __debug__:
_assert_have_json()
+ if 'padded' in kwargs:
+ if isinstance(kwargs['padded'], str):
+ callback = request.args.get(kwargs['padded']) or 'jsonp'
+ else:
+ callback = request.args.get('callback') or \
+ request.args.get('jsonp') or 'jsonp'
+ del kwargs['padded']
+ json_str = json.dumps(dict(*args, **kwargs), indent=None)
+ content = str(callback) + "(" + json_str + ")"
+ return current_app.response_class(content, mimetype='text/javascript')
return current_app.response_class(json.dumps(dict(*args, **kwargs),
indent=None if request.is_xhr else 2), mimetype='application/json')
diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py
index 89c6c188fc..44ac90166e 100644
--- a/flask/testsuite/helpers.py
+++ b/flask/testsuite/helpers.py
@@ -61,11 +61,25 @@ def return_kwargs():
@app.route('/dict')
def return_dict():
return flask.jsonify(d)
+ @app.route("/padded")
+ def return_padded_json():
+ return flask.jsonify(d, padded=True)
+ @app.route("/padded_custom")
+ def return_padded_json_custom_callback():
+ return flask.jsonify(d, padded='my_func_name')
c = app.test_client()
for url in '/kw', '/dict':
rv = c.get(url)
self.assert_equal(rv.mimetype, 'application/json')
self.assert_equal(flask.json.loads(rv.data), d)
+ for get_arg in 'callback=funcName', 'jsonp=funcName':
+ rv = c.get('/padded?' + get_arg)
+ self.assert_( rv.data.startswith("funcName(") )
+ self.assert_( rv.data.endswith(")") )
+ rv_json = rv.data.split('(')[1].split(')')[0]
+ self.assert_equal(flask.json.loads(rv_json), d)
+ rv = c.get('/padded_custom?my_func_name=funcName')
+ self.assert_( rv.data.startswith("funcName(") )
def test_json_attr(self):
app = flask.Flask(__name__)
From 09370c3f1c808e9251292bc228b6bef4b1223e93 Mon Sep 17 00:00:00 2001
From: Ned Jackson Lovely
Date: Mon, 12 Mar 2012 15:26:05 -0400
Subject: [PATCH 045/142] Clean up docs and review pull request #384
Spelunking through the issues at the PyCon sprints.
---
flask/helpers.py | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/flask/helpers.py b/flask/helpers.py
index ebe5fca553..ee68ce95fc 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -119,15 +119,16 @@ def get_current_user():
.. versionadded:: 0.2
.. versionadded:: 0.9
- If the argument ``padded`` true than the json object will pad for
- JSONP calls like from jquery. The response mimetype will also change
- to ``text/javascript``.
-
- The json object will pad as javascript function with the function name
- from the request argument ``callback`` or ``jsonp``. If the argument
- ``padded`` a string jsonify will look for the function name in the
- request argument with the name which is equal to ``padded``. Is there
- no function name it will fallback and use ``jsonp`` as function name.
+ If the ``padded`` argument is true, the JSON object will be padded
+ for JSONP calls and the response mimetype will be changed to
+ ``text/javascript``. By default, the request arguments ``callback``
+ and ``jsonp`` will be used as the name for the callback function.
+ This will work with jQuery and most other JavaScript libraries
+ by default.
+
+ If the ``padded`` argument is a string, jsonify will look for
+ the request argument with the same name and use that value as the
+ callback-function name.
"""
if __debug__:
_assert_have_json()
From 27194a01d8f7e4fd913811859ec4051ff99c52f4 Mon Sep 17 00:00:00 2001
From: Ned Jackson Lovely
Date: Mon, 12 Mar 2012 16:02:53 -0400
Subject: [PATCH 046/142] Fix typo in docs.
http://feedback.flask.pocoo.org/message/279
---
docs/quickstart.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 9fde0c2fce..b442af28fa 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -169,8 +169,8 @@ The following converters exist:
.. admonition:: Unique URLs / Redirection Behaviour
Flask's URL rules are based on Werkzeug's routing module. The idea behind
- that module is to ensure beautiful and unique also unique URLs based on
- precedents laid down by Apache and earlier HTTP servers.
+ that module is to ensure beautiful and unique URLs based on precedents
+ laid down by Apache and earlier HTTP servers.
Take these two rules::
From 68f93634de2e25afda209b710002e4c9159fd38e Mon Sep 17 00:00:00 2001
From: Ned Jackson Lovely
Date: Mon, 12 Mar 2012 17:18:27 -0400
Subject: [PATCH 047/142] Second thoughts on mime type
After further review, changing the mime type on jsonp
responses from text/javascript to application/javascript,
with a hat-tip to
http://stackoverflow.com/questions/111302/best-content-type-to-serve-jsonp
---
flask/helpers.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/flask/helpers.py b/flask/helpers.py
index ee68ce95fc..31a0f6932a 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -121,7 +121,7 @@ def get_current_user():
.. versionadded:: 0.9
If the ``padded`` argument is true, the JSON object will be padded
for JSONP calls and the response mimetype will be changed to
- ``text/javascript``. By default, the request arguments ``callback``
+ ``application/javascript``. By default, the request arguments ``callback``
and ``jsonp`` will be used as the name for the callback function.
This will work with jQuery and most other JavaScript libraries
by default.
@@ -141,7 +141,7 @@ def get_current_user():
del kwargs['padded']
json_str = json.dumps(dict(*args, **kwargs), indent=None)
content = str(callback) + "(" + json_str + ")"
- return current_app.response_class(content, mimetype='text/javascript')
+ return current_app.response_class(content, mimetype='application/javascript')
return current_app.response_class(json.dumps(dict(*args, **kwargs),
indent=None if request.is_xhr else 2), mimetype='application/json')
From 71b351173b1440d6ca2dc36284d080fda2a22006 Mon Sep 17 00:00:00 2001
From: "Anton I. Sipos"
Date: Mon, 12 Mar 2012 14:53:24 -0700
Subject: [PATCH 048/142] Move JSONHTTPException and JSONBadRequest to new
module flask.exceptions.
---
flask/exceptions.py | 49 +++++++++++++++++++++++++++++++++++++++++++++
flask/wrappers.py | 39 +-----------------------------------
2 files changed, 50 insertions(+), 38 deletions(-)
create mode 100644 flask/exceptions.py
diff --git a/flask/exceptions.py b/flask/exceptions.py
new file mode 100644
index 0000000000..9ccdedaba1
--- /dev/null
+++ b/flask/exceptions.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.exceptions
+ ~~~~~~~~~~~~
+
+ Flask specific additions to :class:`~werkzeug.exceptions.HTTPException`
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from werkzeug.exceptions import HTTPException, BadRequest
+from .helpers import json
+
+
+class JSONHTTPException(HTTPException):
+ """A base class for HTTP exceptions with ``Content-Type:
+ application/json``.
+
+ The ``description`` attribute of this class must set to a string (*not* an
+ HTML string) which describes the error.
+
+ """
+
+ def get_body(self, environ):
+ """Overrides :meth:`werkzeug.exceptions.HTTPException.get_body` to
+ return the description of this error in JSON format instead of HTML.
+
+ """
+ return json.dumps(dict(description=self.get_description(environ)))
+
+ def get_headers(self, environ):
+ """Returns a list of headers including ``Content-Type:
+ application/json``.
+
+ """
+ return [('Content-Type', 'application/json')]
+
+
+class JSONBadRequest(JSONHTTPException, BadRequest):
+ """Represents an HTTP ``400 Bad Request`` error whose body contains an
+ error message in JSON format instead of HTML format (as in the superclass).
+
+ """
+
+ #: The description of the error which occurred as a string.
+ description = (
+ 'The browser (or proxy) sent a request that this server could not '
+ 'understand.'
+ )
diff --git a/flask/wrappers.py b/flask/wrappers.py
index 3df697f75d..541d26ef3c 100644
--- a/flask/wrappers.py
+++ b/flask/wrappers.py
@@ -10,51 +10,14 @@
"""
from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
-from werkzeug.exceptions import BadRequest, HTTPException
from werkzeug.utils import cached_property
+from .exceptions import JSONBadRequest
from .debughelpers import attach_enctype_error_multidict
from .helpers import json, _assert_have_json
from .globals import _request_ctx_stack
-class JSONHTTPException(HTTPException):
- """A base class for HTTP exceptions with ``Content-Type:
- application/json``.
-
- The ``description`` attribute of this class must set to a string (*not* an
- HTML string) which describes the error.
-
- """
-
- def get_body(self, environ):
- """Overrides :meth:`werkzeug.exceptions.HTTPException.get_body` to
- return the description of this error in JSON format instead of HTML.
-
- """
- return json.dumps(dict(description=self.get_description(environ)))
-
- def get_headers(self, environ):
- """Returns a list of headers including ``Content-Type:
- application/json``.
-
- """
- return [('Content-Type', 'application/json')]
-
-
-class JSONBadRequest(JSONHTTPException, BadRequest):
- """Represents an HTTP ``400 Bad Request`` error whose body contains an
- error message in JSON format instead of HTML format (as in the superclass).
-
- """
-
- #: The description of the error which occurred as a string.
- description = (
- 'The browser (or proxy) sent a request that this server could not '
- 'understand.'
- )
-
-
class Request(RequestBase):
"""The request object used by default in Flask. Remembers the
matched endpoint and view arguments.
From 74a72e86addd80a060f1abf9fe51bfc3f5d5be8b Mon Sep 17 00:00:00 2001
From: Max
Date: Mon, 12 Mar 2012 14:58:26 -0700
Subject: [PATCH 049/142] Changed some things in the foreward to diminish its
discouragement.
---
docs/foreword.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/foreword.rst b/docs/foreword.rst
index 7678d0144b..539f289741 100644
--- a/docs/foreword.rst
+++ b/docs/foreword.rst
@@ -8,7 +8,7 @@ should or should not be using it.
What does "micro" mean?
-----------------------
-To me, the "micro" in microframework refers not only to the simplicity and
+As Flask considers it, the "micro" in microframework refers not only to the simplicity and
small size of the framework, but also the fact that it does not make many
decisions for you. While Flask does pick a templating engine for you, we
won't make such decisions for your datastore or other parts.
@@ -55,7 +55,7 @@ section about :ref:`design`.
Web Development is Dangerous
----------------------------
-I'm not joking. Well, maybe a little. If you write a web
+If you write a web
application, you are probably allowing users to register and leave their
data on your server. The users are entrusting you with data. And even if
you are the only user that might leave data in your application, you still
From d8c2ec4cd863112af4c55e1044c8d3024d58f21a Mon Sep 17 00:00:00 2001
From: Max
Date: Mon, 12 Mar 2012 15:03:26 -0700
Subject: [PATCH 050/142] Fixed linebreaks.
---
docs/foreword.rst | 154 ++++++++++++++++++++++++----------------------
1 file changed, 79 insertions(+), 75 deletions(-)
diff --git a/docs/foreword.rst b/docs/foreword.rst
index 539f289741..1fa214e624 100644
--- a/docs/foreword.rst
+++ b/docs/foreword.rst
@@ -1,100 +1,104 @@
-Foreword
+Foreword
========
-Read this before you get started with Flask. This hopefully answers some
-questions about the purpose and goals of the project, and when you
-should or should not be using it.
+Read this before you get started with Flask. This hopefully answers
+some questions about the purpose and goals of the project, and when
+you should or should not be using it.
-What does "micro" mean?
+What does "micro" mean?
-----------------------
-As Flask considers it, the "micro" in microframework refers not only to the simplicity and
-small size of the framework, but also the fact that it does not make many
-decisions for you. While Flask does pick a templating engine for you, we
-won't make such decisions for your datastore or other parts.
+As Flask considers it, the "micro" in microframework refers not only
+to the simplicity and small size of the framework, but also the fact
+that it does not make many decisions for you. While Flask does pick a
+templating engine for you, we won't make such decisions for your
+datastore or other parts.
-However, to us the term “micro” does not mean that the whole implementation
-has to fit into a single Python file.
+However, to us the term “micro” does not mean that the whole
+implementation has to fit into a single Python file.
One of the design decisions with Flask was that simple tasks should be
-simple; they should not take a lot of code and yet they should not limit you.
-Because of that we made a few design choices that some people might find
-surprising or unorthodox. For example, Flask uses thread-local objects
-internally so that you don't have to pass objects around from function to
-function within a request in order to stay threadsafe. While this is a
-really easy approach and saves you a lot of time, it might also cause some
-troubles for very large applications because changes on these thread-local
-objects can happen anywhere in the same thread. In order to solve these
-problems we don't hide the thread locals for you but instead embrace them
-and provide you with a lot of tools to make it as pleasant as possible to
-work with them.
+simple; they should not take a lot of code and yet they should not
+limit you. Because of that we made a few design choices that some
+people might find surprising or unorthodox. For example, Flask uses
+thread-local objects internally so that you don't have to pass objects
+around from function to function within a request in order to stay
+threadsafe. While this is a really easy approach and saves you a lot
+of time, it might also cause some troubles for very large applications
+because changes on these thread-local objects can happen anywhere in
+the same thread. In order to solve these problems we don't hide the
+thread locals for you but instead embrace them and provide you with a
+lot of tools to make it as pleasant as possible to work with them.
Flask is also based on convention over configuration, which means that
-many things are preconfigured. For example, by convention templates and
-static files are stored in subdirectories within the application's Python source tree.
-While this can be changed you usually don't have to.
+many things are preconfigured. For example, by convention templates
+and static files are stored in subdirectories within the application's
+Python source tree. While this can be changed you usually don't have
+to.
-The main reason Flask is called a "microframework" is the idea
-to keep the core simple but extensible. There is no database abstraction
+The main reason Flask is called a "microframework" is the idea to keep
+the core simple but extensible. There is no database abstraction
layer, no form validation or anything else where different libraries
-already exist that can handle that. However Flask supports
-extensions to add such functionality to your application as if it
-was implemented in Flask itself. There are currently extensions for
-object-relational mappers, form validation, upload handling, various open
-authentication technologies and more.
-
-Since Flask is based on a very solid foundation there is not a lot of code
-in Flask itself. As such it's easy to adapt even for large applications
-and we are making sure that you can either configure it as much as
-possible by subclassing things or by forking the entire codebase. If you
-are interested in that, check out the :ref:`becomingbig` chapter.
+already exist that can handle that. However Flask supports extensions
+to add such functionality to your application as if it was implemented
+in Flask itself. There are currently extensions for object-relational
+mappers, form validation, upload handling, various open authentication
+technologies and more.
+
+Since Flask is based on a very solid foundation there is not a lot of
+code in Flask itself. As such it's easy to adapt even for large
+applications and we are making sure that you can either configure it
+as much as possible by subclassing things or by forking the entire
+codebase. If you are interested in that, check out the
+:ref:`becomingbig` chapter.
If you are curious about the Flask design principles, head over to the
section about :ref:`design`.
-Web Development is Dangerous
-----------------------------
+Web Development is Dangerous ----------------------------
-If you write a web
-application, you are probably allowing users to register and leave their
-data on your server. The users are entrusting you with data. And even if
-you are the only user that might leave data in your application, you still
-want that data to be stored securely.
+If you write a web application, you are probably allowing users to
+register and leave their data on your server. The users are
+entrusting you with data. And even if you are the only user that
+might leave data in your application, you still want that data to be
+stored securely.
-Unfortunately, there are many ways the security of a web application can be
-compromised. Flask protects you against one of the most common security
-problems of modern web applications: cross-site scripting (XSS). Unless
-you deliberately mark insecure HTML as secure, Flask and the underlying
-Jinja2 template engine have you covered. But there are many more ways to
-cause security problems.
+Unfortunately, there are many ways the security of a web application
+can be compromised. Flask protects you against one of the most common
+security problems of modern web applications: cross-site scripting
+(XSS). Unless you deliberately mark insecure HTML as secure, Flask
+and the underlying Jinja2 template engine have you covered. But there
+are many more ways to cause security problems.
The documentation will warn you about aspects of web development that
-require attention to security. Some of these security concerns
-are far more complex than one might think, and we all sometimes underestimate
-the likelihood that a vulnerability will be exploited - until a clever
-attacker figures out a way to exploit our applications. And don't think
-that your application is not important enough to attract an attacker.
-Depending on the kind of attack, chances are that automated bots are
-probing for ways to fill your database with spam, links to malicious
-software, and the like.
+require attention to security. Some of these security concerns are
+far more complex than one might think, and we all sometimes
+underestimate the likelihood that a vulnerability will be exploited -
+until a clever attacker figures out a way to exploit our applications.
+And don't think that your application is not important enough to
+attract an attacker. Depending on the kind of attack, chances are that
+automated bots are probing for ways to fill your database with spam,
+links to malicious software, and the like.
So always keep security in mind when doing web development.
-The Status of Python 3
+The Status of Python 3
----------------------
-Currently the Python community is in the process of improving libraries to
-support the new iteration of the Python programming language. While the
-situation is greatly improving there are still some issues that make it
-hard for us to switch over to Python 3 just now. These problems are
-partially caused by changes in the language that went unreviewed for too
-long, partially also because we have not quite worked out how the lower-
-level API should change to account for the Unicode differences in Python 3.
-
-Werkzeug and Flask will be ported to Python 3 as soon as a solution for
-the changes is found, and we will provide helpful tips how to upgrade
-existing applications to Python 3. Until then, we strongly recommend
-using Python 2.6 and 2.7 with activated Python 3 warnings during
-development. If you plan on upgrading to Python 3 in the near future we
-strongly recommend that you read `How to write forwards compatible
-Python code `_.
+Currently the Python community is in the process of improving
+libraries to support the new iteration of the Python programming
+language. While the situation is greatly improving there are still
+some issues that make it hard for us to switch over to Python 3 just
+now. These problems are partially caused by changes in the language
+that went unreviewed for too long, partially also because we have not
+quite worked out how the lower- level API should change to account for
+the Unicode differences in Python 3.
+
+Werkzeug and Flask will be ported to Python 3 as soon as a solution
+for the changes is found, and we will provide helpful tips how to
+upgrade existing applications to Python 3. Until then, we strongly
+recommend using Python 2.6 and 2.7 with activated Python 3 warnings
+during development. If you plan on upgrading to Python 3 in the near
+future we strongly recommend that you read `How to write forwards
+compatible Python code `_.
From c78070d8623fb6f40bf4ef20a1109083ca79ef7a Mon Sep 17 00:00:00 2001
From: Max
Date: Mon, 12 Mar 2012 15:08:52 -0700
Subject: [PATCH 051/142] Wrapped paragraphs; changed some words.
---
docs/foreword.rst | 153 ++++++++++++++++++++++------------------------
1 file changed, 74 insertions(+), 79 deletions(-)
diff --git a/docs/foreword.rst b/docs/foreword.rst
index 1fa214e624..5751ccb751 100644
--- a/docs/foreword.rst
+++ b/docs/foreword.rst
@@ -1,104 +1,99 @@
-Foreword
+Foreword
========
-Read this before you get started with Flask. This hopefully answers
-some questions about the purpose and goals of the project, and when
-you should or should not be using it.
+Read this before you get started with Flask. This hopefully answers some
+questions about the purpose and goals of the project, and when you
+should or should not be using it.
-What does "micro" mean?
+What does "micro" mean?
-----------------------
-As Flask considers it, the "micro" in microframework refers not only
-to the simplicity and small size of the framework, but also the fact
-that it does not make many decisions for you. While Flask does pick a
-templating engine for you, we won't make such decisions for your
-datastore or other parts.
+Flask considers the "micro" in microframework to refer not only to the
+simplicity and small size of the framework, but also to the fact that it does
+not make many decisions for you. While Flask does pick a templating engine
+for you, we won't make such decisions for your datastore or other parts.
-However, to us the term “micro” does not mean that the whole
-implementation has to fit into a single Python file.
+However, to us the term “micro” does not mean that the whole implementation
+has to fit into a single Python file.
One of the design decisions with Flask was that simple tasks should be
-simple; they should not take a lot of code and yet they should not
-limit you. Because of that we made a few design choices that some
-people might find surprising or unorthodox. For example, Flask uses
-thread-local objects internally so that you don't have to pass objects
-around from function to function within a request in order to stay
-threadsafe. While this is a really easy approach and saves you a lot
-of time, it might also cause some troubles for very large applications
-because changes on these thread-local objects can happen anywhere in
-the same thread. In order to solve these problems we don't hide the
-thread locals for you but instead embrace them and provide you with a
-lot of tools to make it as pleasant as possible to work with them.
+simple; they should not take a lot of code and yet they should not limit you.
+Because of that we made a few design choices that some people might find
+surprising or unorthodox. For example, Flask uses thread-local objects
+internally so that you don't have to pass objects around from function to
+function within a request in order to stay threadsafe. While this is a
+really easy approach and saves you a lot of time, it might also cause some
+troubles for very large applications because changes on these thread-local
+objects can happen anywhere in the same thread. In order to solve these
+problems we don't hide the thread locals for you but instead embrace them
+and provide you with a lot of tools to make it as pleasant as possible to
+work with them.
Flask is also based on convention over configuration, which means that
-many things are preconfigured. For example, by convention templates
-and static files are stored in subdirectories within the application's
-Python source tree. While this can be changed you usually don't have
-to.
+many things are preconfigured. For example, by convention templates and
+static files are stored in subdirectories within the application's Python source tree.
+While this can be changed you usually don't have to.
-The main reason Flask is called a "microframework" is the idea to keep
-the core simple but extensible. There is no database abstraction
+The main reason Flask is called a "microframework" is the idea
+to keep the core simple but extensible. There is no database abstraction
layer, no form validation or anything else where different libraries
-already exist that can handle that. However Flask supports extensions
-to add such functionality to your application as if it was implemented
-in Flask itself. There are currently extensions for object-relational
-mappers, form validation, upload handling, various open authentication
-technologies and more.
-
-Since Flask is based on a very solid foundation there is not a lot of
-code in Flask itself. As such it's easy to adapt even for large
-applications and we are making sure that you can either configure it
-as much as possible by subclassing things or by forking the entire
-codebase. If you are interested in that, check out the
-:ref:`becomingbig` chapter.
+already exist that can handle that. However Flask supports
+extensions to add such functionality to your application as if it
+was implemented in Flask itself. There are currently extensions for
+object-relational mappers, form validation, upload handling, various open
+authentication technologies and more.
+
+Since Flask is based on a very solid foundation there is not a lot of code
+in Flask itself. As such it's easy to adapt even for large applications
+and we are making sure that you can either configure it as much as
+possible by subclassing things or by forking the entire codebase. If you
+are interested in that, check out the :ref:`becomingbig` chapter.
If you are curious about the Flask design principles, head over to the
section about :ref:`design`.
-Web Development is Dangerous ----------------------------
+Web Development is Dangerous
+----------------------------
-If you write a web application, you are probably allowing users to
-register and leave their data on your server. The users are
-entrusting you with data. And even if you are the only user that
-might leave data in your application, you still want that data to be
-stored securely.
+If you write a web application, you are probably allowing users to register
+and leave their data on your server. The users are entrusting you with data.
+And even if you are the only user that might leave data in your application,
+you still want that data to be stored securely.
-Unfortunately, there are many ways the security of a web application
-can be compromised. Flask protects you against one of the most common
-security problems of modern web applications: cross-site scripting
-(XSS). Unless you deliberately mark insecure HTML as secure, Flask
-and the underlying Jinja2 template engine have you covered. But there
-are many more ways to cause security problems.
+Unfortunately, there are many ways the security of a web application can be
+compromised. Flask protects you against one of the most common security
+problems of modern web applications: cross-site scripting (XSS). Unless
+you deliberately mark insecure HTML as secure, Flask and the underlying
+Jinja2 template engine have you covered. But there are many more ways to
+cause security problems.
The documentation will warn you about aspects of web development that
-require attention to security. Some of these security concerns are
-far more complex than one might think, and we all sometimes
-underestimate the likelihood that a vulnerability will be exploited -
-until a clever attacker figures out a way to exploit our applications.
-And don't think that your application is not important enough to
-attract an attacker. Depending on the kind of attack, chances are that
-automated bots are probing for ways to fill your database with spam,
-links to malicious software, and the like.
+require attention to security. Some of these security concerns
+are far more complex than one might think, and we all sometimes underestimate
+the likelihood that a vulnerability will be exploited - until a clever
+attacker figures out a way to exploit our applications. And don't think
+that your application is not important enough to attract an attacker.
+Depending on the kind of attack, chances are that automated bots are
+probing for ways to fill your database with spam, links to malicious
+software, and the like.
So always keep security in mind when doing web development.
-The Status of Python 3
+The Status of Python 3
----------------------
-Currently the Python community is in the process of improving
-libraries to support the new iteration of the Python programming
-language. While the situation is greatly improving there are still
-some issues that make it hard for us to switch over to Python 3 just
-now. These problems are partially caused by changes in the language
-that went unreviewed for too long, partially also because we have not
-quite worked out how the lower- level API should change to account for
-the Unicode differences in Python 3.
-
-Werkzeug and Flask will be ported to Python 3 as soon as a solution
-for the changes is found, and we will provide helpful tips how to
-upgrade existing applications to Python 3. Until then, we strongly
-recommend using Python 2.6 and 2.7 with activated Python 3 warnings
-during development. If you plan on upgrading to Python 3 in the near
-future we strongly recommend that you read `How to write forwards
-compatible Python code `_.
+Currently the Python community is in the process of improving libraries to
+support the new iteration of the Python programming language. While the
+situation is greatly improving there are still some issues that make it
+hard for us to switch over to Python 3 just now. These problems are
+partially caused by changes in the language that went unreviewed for too
+long, partially also because we have not quite worked out how the lower-
+level API should change to account for the Unicode differences in Python 3.
+
+Werkzeug and Flask will be ported to Python 3 as soon as a solution for
+the changes is found, and we will provide helpful tips how to upgrade
+existing applications to Python 3. Until then, we strongly recommend
+using Python 2.6 and 2.7 with activated Python 3 warnings during
+development. If you plan on upgrading to Python 3 in the near future we
+strongly recommend that you read `How to write forwards compatible
+Python code `_.
From 756a5565ea395c5113e8e9cf21b39060548aa5ba Mon Sep 17 00:00:00 2001
From: wilsaj
Date: Mon, 12 Mar 2012 17:21:49 -0500
Subject: [PATCH 052/142] docfix: wrong converter name: unicode -> string
---
docs/api.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/api.rst b/docs/api.rst
index ec7e4f6381..fe871112f3 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -511,7 +511,7 @@ Variable parts are passed to the view function as keyword arguments.
The following converters are available:
=========== ===============================================
-`unicode` accepts any text without a slash (the default)
+`string` accepts any text without a slash (the default)
`int` accepts integers
`float` like `int` but for floating point values
`path` like the default but also accepts slashes
From a77938837c6466edfde7f1708ef56587189a5e2b Mon Sep 17 00:00:00 2001
From: wilsaj
Date: Mon, 12 Mar 2012 17:21:49 -0500
Subject: [PATCH 053/142] docfix: wrong converter name: unicode -> string fixes
#364
---
docs/api.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/api.rst b/docs/api.rst
index ec7e4f6381..fe871112f3 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -511,7 +511,7 @@ Variable parts are passed to the view function as keyword arguments.
The following converters are available:
=========== ===============================================
-`unicode` accepts any text without a slash (the default)
+`string` accepts any text without a slash (the default)
`int` accepts integers
`float` like `int` but for floating point values
`path` like the default but also accepts slashes
From 8d1546f8e64e093847ad3d5579ad5f9b7c3d0e45 Mon Sep 17 00:00:00 2001
From: James Saryerwinnie
Date: Mon, 12 Mar 2012 16:28:39 -0700
Subject: [PATCH 054/142] Reword the docs for writing a flask extension
There was a minor bug in the example extension that's
been fixed.
I also updated the description of the fixed code accordingly, and
expanded on the usage of _request_ctx_stack.top for adding data
that should be accesible to view functions.
I verified that the existing code as is works as expected.
---
docs/extensiondev.rst | 119 +++++++++++++++++-------------------------
1 file changed, 49 insertions(+), 70 deletions(-)
diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst
index 074d06ab6d..ab038e0c28 100644
--- a/docs/extensiondev.rst
+++ b/docs/extensiondev.rst
@@ -125,9 +125,8 @@ Initializing Extensions
-----------------------
Many extensions will need some kind of initialization step. For example,
-consider your application is currently connecting to SQLite like the
-documentation suggests (:ref:`sqlite3`) you will need to provide a few
-functions and before / after request handlers. So how does the extension
+consider an application that's currently connecting to SQLite like the
+documentation suggests (:ref:`sqlite3`). So how does the extension
know the name of the application object?
Quite simple: you pass it to it.
@@ -135,12 +134,14 @@ Quite simple: you pass it to it.
There are two recommended ways for an extension to initialize:
initialization functions:
+
If your extension is called `helloworld` you might have a function
called ``init_helloworld(app[, extra_args])`` that initializes the
extension for that application. It could attach before / after
handlers etc.
classes:
+
Classes work mostly like initialization functions but can later be
used to further change the behaviour. For an example look at how the
`OAuth extension`_ works: there is an `OAuth` object that provides
@@ -148,22 +149,29 @@ classes:
a remote application that uses OAuth.
What to use depends on what you have in mind. For the SQLite 3 extension
-we will use the class-based approach because it will provide users with a
-manager object that handles opening and closing database connections.
+we will use the class-based approach because it will provide users with an
+object that handles opening and closing database connections.
The Extension Code
------------------
Here's the contents of the `flask_sqlite3.py` for copy/paste::
- from __future__ import absolute_import
import sqlite3
from flask import _request_ctx_stack
+
class SQLite3(object):
- def __init__(self, app):
+ def __init__(self, app=None):
+ if app is not None:
+ self.app = app
+ self.init_app(self.app)
+ else:
+ self.app = None
+
+ def init_app(self, app):
self.app = app
self.app.config.setdefault('SQLITE3_DATABASE', ':memory:')
self.app.teardown_request(self.teardown_request)
@@ -180,27 +188,29 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste::
ctx = _request_ctx_stack.top
ctx.sqlite3_db.close()
- def get_db(self):
+ @property
+ def connection(self):
ctx = _request_ctx_stack.top
if ctx is not None:
return ctx.sqlite3_db
+
So here's what these lines of code do:
-1. The ``__future__`` import is necessary to activate absolute imports.
- Otherwise we could not call our module `sqlite3.py` and import the
- top-level `sqlite3` module which actually implements the connection to
- SQLite.
-2. We create a class for our extension that requires a supplied `app` object,
- sets a configuration for the database if it's not there
- (:meth:`dict.setdefault`), and attaches `before_request` and
- `teardown_request` handlers.
-3. Next, we define a `connect` function that opens a database connection.
+1. The ``__init__`` method takes an optional app object and, if supplied, will
+ call ``init_app``.
+2. The ``init_app`` method exists so that the ``SQLite3`` object can be
+ instantiated without requiring an app object. This method supports the
+ factory pattern for creating applications. The ``init_app`` will set the
+ configuration for the database, defaulting to an in memory database if
+ no configuration is supplied. In addition, the ``init_app`` method attaches
+ ``before_request`` and ``teardown_request`` handlers.
+3. Next, we define a ``connect`` method that opens a database connection.
4. Then we set up the request handlers we bound to the app above. Note here
that we're attaching our database connection to the top request context via
- `_request_ctx_stack.top`. Extensions should use the top context and not the
- `g` object to store things like database connections.
-5. Finally, we add a `get_db` function that simplifies access to the context's
+ ``_request_ctx_stack.top``. Extensions should use the top context and not the
+ ``g`` object to store things like database connections.
+5. Finally, we add a ``connection`` property that simplifies access to the context's
database.
So why did we decide on a class-based approach here? Because using our
@@ -211,68 +221,36 @@ extension looks something like this::
app = Flask(__name__)
app.config.from_pyfile('the-config.cfg')
- manager = SQLite3(app)
- db = manager.get_db()
+ db = SQLite3(app)
You can then use the database from views like this::
@app.route('/')
def show_all():
- cur = db.cursor()
+ cur = db.connection.cursor()
cur.execute(...)
-Opening a database connection from outside a view function is simple.
-
->>> from yourapplication import db
->>> cur = db.cursor()
->>> cur.execute(...)
-
-Adding an `init_app` Function
------------------------------
-
-In practice, you'll almost always want to permit users to initialize your
-extension and provide an app object after the fact. This can help avoid
-circular import problems when a user is breaking their app into multiple files.
-Our extension could add an `init_app` function as follows::
-
- class SQLite3(object):
-
- def __init__(self, app=None):
- if app is not None:
- self.app = app
- self.init_app(self.app)
- else:
- self.app = None
-
- def init_app(self, app):
- self.app = app
- self.app.config.setdefault('SQLITE3_DATABASE', ':memory:')
- self.app.teardown_request(self.teardown_request)
- self.app.before_request(self.before_request)
-
- def connect(self):
- return sqlite3.connect(app.config['SQLITE3_DATABASE'])
-
- def before_request(self):
- ctx = _request_ctx_stack.top
- ctx.sqlite3_db = self.connect()
+Additionally, the ``init_app`` method is used to support the factory pattern
+for creating apps::
- def teardown_request(self, exception):
- ctx = _request_ctx_stack.top
- ctx.sqlite3_db.close()
+ db = Sqlite3()
+ # Then later on.
+ app = create_app('the-config.cfg')
+ db.init_app(app)
- def get_db(self):
- ctx = _request_ctx_stack.top
- if ctx is not None:
- return ctx.sqlite3_db
+Keep in mind that supporting this factory pattern for creating apps is required
+for approved flask extensions (described below).
-The user could then initialize the extension in one file::
- manager = SQLite3()
+Using _request_ctx_stack
+------------------------
-and bind their app to the extension in another file::
-
- manager.init_app(app)
+In the example above, before every request, a ``sqlite3_db`` variable is assigned
+to ``_request_ctx_stack.top``. In a view function, this variable is accessible
+using the ``connection`` property of ``SQLite3``. During the teardown of a
+request, the ``sqlite3_db`` connection is closed. By using this pattern, the
+*same* connection to the sqlite3 database is accessible to anything that needs it
+for the duration of the request.
End-Of-Request Behavior
-----------------------
@@ -292,6 +270,7 @@ pattern is a good way to support both::
else:
app.after_request(close_connection)
+
Strictly speaking the above code is wrong, because teardown functions are
passed the exception and typically don't return anything. However because
the return value is discarded this will just work assuming that the code
From 8f568cfc19f5b5f2aa59b06d4e2b5b8d31423605 Mon Sep 17 00:00:00 2001
From: Max
Date: Mon, 12 Mar 2012 17:12:55 -0700
Subject: [PATCH 055/142] Split foreword into two files; edited lots.
---
docs/advanced_foreword.rst | 67 +++++++++++++++++++
docs/foreword.rst | 134 +++++++++++++------------------------
2 files changed, 112 insertions(+), 89 deletions(-)
create mode 100644 docs/advanced_foreword.rst
diff --git a/docs/advanced_foreword.rst b/docs/advanced_foreword.rst
new file mode 100644
index 0000000000..cc1a1843e8
--- /dev/null
+++ b/docs/advanced_foreword.rst
@@ -0,0 +1,67 @@
+Foreword for Experienced Programmers
+====================================
+
+This chapter is for programmers who have worked with other frameworks in the
+past, and who may have more specific or esoteric concerns that the typical
+user.
+
+Threads in Flask
+----------------
+
+One of the design decisions with Flask was that simple tasks should be simple;
+they should not take a lot of code and yet they should not limit you. Because
+of that we made a few design choices that some people might find surprising or
+unorthodox. For example, Flask uses thread-local objects internally so that
+you don’t have to pass objects around from function to function within a
+request in order to stay threadsafe. While this is a really easy approach and
+saves you a lot of time, it might also cause some troubles for very large
+applications because changes on these thread-local objects can happen anywhere
+in the same thread. In order to solve these problems we don’t hide the thread
+locals for you but instead embrace them and provide you with a lot of tools to
+make it as pleasant as possible to work with them.
+
+Web Development is Dangerous
+----------------------------
+
+If you write a web application, you are probably allowing users to register
+and leave their data on your server. The users are entrusting you with data.
+And even if you are the only user that might leave data in your application,
+you still want that data to be stored securely.
+
+Unfortunately, there are many ways the security of a web application can be
+compromised. Flask protects you against one of the most common security
+problems of modern web applications: cross-site scripting (XSS). Unless
+you deliberately mark insecure HTML as secure, Flask and the underlying
+Jinja2 template engine have you covered. But there are many more ways to
+cause security problems.
+
+The documentation will warn you about aspects of web development that
+require attention to security. Some of these security concerns
+are far more complex than one might think, and we all sometimes underestimate
+the likelihood that a vulnerability will be exploited - until a clever
+attacker figures out a way to exploit our applications. And don't think
+that your application is not important enough to attract an attacker.
+Depending on the kind of attack, chances are that automated bots are
+probing for ways to fill your database with spam, links to malicious
+software, and the like.
+
+So always keep security in mind when doing web development.
+
+The Status of Python 3
+----------------------
+
+Currently the Python community is in the process of improving libraries to
+support the new iteration of the Python programming language. While the
+situation is greatly improving there are still some issues that make it
+hard for us to switch over to Python 3 just now. These problems are
+partially caused by changes in the language that went unreviewed for too
+long, partially also because we have not quite worked out how the lower-
+level API should change to account for the Unicode differences in Python 3.
+
+Werkzeug and Flask will be ported to Python 3 as soon as a solution for
+the changes is found, and we will provide helpful tips how to upgrade
+existing applications to Python 3. Until then, we strongly recommend
+using Python 2.6 and 2.7 with activated Python 3 warnings during
+development. If you plan on upgrading to Python 3 in the near future we
+strongly recommend that you read `How to write forwards compatible
+Python code `_.
diff --git a/docs/foreword.rst b/docs/foreword.rst
index 5751ccb751..b186aba655 100644
--- a/docs/foreword.rst
+++ b/docs/foreword.rst
@@ -8,92 +8,48 @@ should or should not be using it.
What does "micro" mean?
-----------------------
-Flask considers the "micro" in microframework to refer not only to the
-simplicity and small size of the framework, but also to the fact that it does
-not make many decisions for you. While Flask does pick a templating engine
-for you, we won't make such decisions for your datastore or other parts.
-
-However, to us the term “micro” does not mean that the whole implementation
-has to fit into a single Python file.
-
-One of the design decisions with Flask was that simple tasks should be
-simple; they should not take a lot of code and yet they should not limit you.
-Because of that we made a few design choices that some people might find
-surprising or unorthodox. For example, Flask uses thread-local objects
-internally so that you don't have to pass objects around from function to
-function within a request in order to stay threadsafe. While this is a
-really easy approach and saves you a lot of time, it might also cause some
-troubles for very large applications because changes on these thread-local
-objects can happen anywhere in the same thread. In order to solve these
-problems we don't hide the thread locals for you but instead embrace them
-and provide you with a lot of tools to make it as pleasant as possible to
-work with them.
-
-Flask is also based on convention over configuration, which means that
-many things are preconfigured. For example, by convention templates and
-static files are stored in subdirectories within the application's Python source tree.
-While this can be changed you usually don't have to.
-
-The main reason Flask is called a "microframework" is the idea
-to keep the core simple but extensible. There is no database abstraction
-layer, no form validation or anything else where different libraries
-already exist that can handle that. However Flask supports
-extensions to add such functionality to your application as if it
-was implemented in Flask itself. There are currently extensions for
-object-relational mappers, form validation, upload handling, various open
-authentication technologies and more.
-
-Since Flask is based on a very solid foundation there is not a lot of code
-in Flask itself. As such it's easy to adapt even for large applications
-and we are making sure that you can either configure it as much as
-possible by subclassing things or by forking the entire codebase. If you
-are interested in that, check out the :ref:`becomingbig` chapter.
-
-If you are curious about the Flask design principles, head over to the
-section about :ref:`design`.
-
-Web Development is Dangerous
-----------------------------
-
-If you write a web application, you are probably allowing users to register
-and leave their data on your server. The users are entrusting you with data.
-And even if you are the only user that might leave data in your application,
-you still want that data to be stored securely.
-
-Unfortunately, there are many ways the security of a web application can be
-compromised. Flask protects you against one of the most common security
-problems of modern web applications: cross-site scripting (XSS). Unless
-you deliberately mark insecure HTML as secure, Flask and the underlying
-Jinja2 template engine have you covered. But there are many more ways to
-cause security problems.
-
-The documentation will warn you about aspects of web development that
-require attention to security. Some of these security concerns
-are far more complex than one might think, and we all sometimes underestimate
-the likelihood that a vulnerability will be exploited - until a clever
-attacker figures out a way to exploit our applications. And don't think
-that your application is not important enough to attract an attacker.
-Depending on the kind of attack, chances are that automated bots are
-probing for ways to fill your database with spam, links to malicious
-software, and the like.
-
-So always keep security in mind when doing web development.
-
-The Status of Python 3
-----------------------
-
-Currently the Python community is in the process of improving libraries to
-support the new iteration of the Python programming language. While the
-situation is greatly improving there are still some issues that make it
-hard for us to switch over to Python 3 just now. These problems are
-partially caused by changes in the language that went unreviewed for too
-long, partially also because we have not quite worked out how the lower-
-level API should change to account for the Unicode differences in Python 3.
-
-Werkzeug and Flask will be ported to Python 3 as soon as a solution for
-the changes is found, and we will provide helpful tips how to upgrade
-existing applications to Python 3. Until then, we strongly recommend
-using Python 2.6 and 2.7 with activated Python 3 warnings during
-development. If you plan on upgrading to Python 3 in the near future we
-strongly recommend that you read `How to write forwards compatible
-Python code `_.
+“Micro” does not mean that your whole web application has to fit into
+a single Python file (although it certainly can). Nor does it mean
+that Flask is lacking in functionality. The "micro" in microframework
+means Flask aims to keep the core simple but extensible. Flask won't make
+many decisions for you, such as what database to use. Those decisions that
+it does make, such as what templating engine to use, are easy to change.
+Everything else is up to you, so that Flask can be everything you need
+and nothing you don't.
+
+By default, Flask does not include a database abstraction layer, form
+validation or anything else where different libraries already exist that can
+handle that. Instead, FLask extensions add such functionality to your
+application as if it was implemented in Flask itself. Numerous extensions
+provide database integration, form validation, upload handling, various open
+authentication technologies, and more. Flask may be "micro", but the
+possibilities are endless.
+
+Convention over Configuration
+-----------------------------
+
+Flask is based on convention over configuration, which means that many things
+are preconfigured. For example, by convention templates and static files are
+stored in subdirectories within the application's Python source tree. While
+this can be changed you usually don't have to. We want to minimize the time
+you need to spend in order to get up and running, without assuming things
+about your needs.
+
+Growing Up
+----------
+
+Since Flask is based on a very solid foundation there is not a lot of code in
+Flask itself. As such it's easy to adapt even for large applications and we
+are making sure that you can either configure it as much as possible by
+subclassing things or by forking the entire codebase. If you are interested
+in that, check out the :ref:`becomingbig` chapter.
+
+If you are curious about the Flask design principles, head over to the section
+about :ref:`design`.
+
+For the Stalwart and Wizened...
+-------------------------------
+
+If you're more curious about the minutiae of Flask's implementation, and
+whether its structure is right for your needs, read the
+:ref:`advanced_foreword`.
From 3bf1750b5dfde8890eab52850bf2e6c0a3de65cf Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Tue, 13 Mar 2012 12:12:47 -0700
Subject: [PATCH 056/142] Tighten quickstart deployment docs.
---
docs/deploying/index.rst | 3 +++
docs/quickstart.rst | 21 ++++++++++++++-------
2 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst
index d258df89aa..1b4189c3d8 100644
--- a/docs/deploying/index.rst
+++ b/docs/deploying/index.rst
@@ -13,6 +13,9 @@ If you have a different WSGI server look up the server documentation
about how to use a WSGI app with it. Just remember that your
:class:`Flask` application object is the actual WSGI application.
+For hosted options to get up and running quickly, see
+:ref:`quickstart_deployment` in the Quickstart.
+
.. toctree::
:maxdepth: 2
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 8ff7f3cf1d..0d8c5b738d 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -827,18 +827,25 @@ can do it like this::
from werkzeug.contrib.fixers import LighttpdCGIRootFix
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)
+.. _quickstart_deployment:
+
Deploying to a Web Server
-------------------------
-If you want to make your Flask app available to the Internet at large, `Heroku
-`_ is very easy to set up and will run small Flask
-applications for free. `Check out their tutorial on how to deploy Flask apps on
-their service `_.
-
-There are a number of other websites that will host your Flask app and make it
-easy for you to do so.
+Ready to deploy your new Flask app? To wrap up the quickstart, you can
+immediately deploy to a hosted platform, all of which offer a free plan for
+small projects:
+- `Deploying Flask on Heroku `_
- `Deploying Flask on ep.io `_
+- `Deploying WSGI on dotCloud `_
+ with `Flask-specific notes `_
+
+Other places where you can host your Flask app:
+
- `Deploying Flask on Webfaction `_
- `Deploying Flask on Google App Engine `_
- `Sharing your Localhost Server with Localtunnel `_
+
+If you manage your own hosts and would like to host yourself, see the chapter
+on :ref:`deployment`.
From c1a2e3cf1479382c1d1e5c46cd2d1ca669df5889 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Tue, 13 Mar 2012 13:41:03 -0700
Subject: [PATCH 057/142] Add Rule #0 to extension development.
---
docs/extensiondev.rst | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst
index 5a8d5b169b..d997b2dea9 100644
--- a/docs/extensiondev.rst
+++ b/docs/extensiondev.rst
@@ -326,6 +326,10 @@ new releases. These approved extensions are listed on the `Flask
Extension Registry`_ and marked appropriately. If you want your own
extension to be approved you have to follow these guidelines:
+0. An approved Flask extension requires a maintainer. In the event an
+ extension author would like to move beyond the project, the project should
+ find a new maintainer including full source hosting transition and PyPI
+ access. If no maintainer is available, give access to the Flask core team.
1. An approved Flask extension must provide exactly one package or module
named ``flask_extensionname``. They might also reside inside a
``flaskext`` namespace packages though this is discouraged now.
From 146088d58066f16ef4bc8172f8120402517c34d3 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Tue, 13 Mar 2012 14:37:48 -0700
Subject: [PATCH 058/142] Expand docs on send_file option hook, #433.
---
CHANGES | 4 ++++
flask/helpers.py | 18 ++++++++++++++++--
flask/testsuite/helpers.py | 4 +---
3 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/CHANGES b/CHANGES
index dbf447db87..6b96be9eed 100644
--- a/CHANGES
+++ b/CHANGES
@@ -48,6 +48,10 @@ 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.
Version 0.8.1
diff --git a/flask/helpers.py b/flask/helpers.py
index 4cb918d2f3..9964792b05 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -653,8 +653,22 @@ def jinja_loader(self):
self.template_folder))
def get_static_file_options(self, filename):
- """Function used internally to determine what keyword arguments
- to send to :func:`send_from_directory` for a specific file."""
+ """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)
+ if filename.lower().endswith('.js'):
+ options['cache_timeout'] = 60
+ options['conditional'] = True
+ return options
+
+ .. versionaded:: 0.9
+ """
return {}
def send_static_file(self, filename):
diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py
index c88026d949..423319939e 100644
--- a/flask/testsuite/helpers.py
+++ b/flask/testsuite/helpers.py
@@ -213,8 +213,6 @@ def test_static_file(self):
self.assert_equal(cc.max_age, 12 * 60 * 60)
# override get_static_file_options with some new values and check them
class StaticFileApp(flask.Flask):
- def __init__(self):
- super(StaticFileApp, self).__init__(__name__)
def get_static_file_options(self, filename):
opts = super(StaticFileApp, self).get_static_file_options(filename)
opts['cache_timeout'] = 10
@@ -222,7 +220,7 @@ def get_static_file_options(self, filename):
# keyword arg in the guts
opts['conditional'] = True
return opts
- app = StaticFileApp()
+ app = StaticFileApp(__name__)
with app.test_request_context():
rv = app.send_static_file('index.html')
cc = parse_cache_control_header(rv.headers['Cache-Control'])
From d94efc6db63516b7f72e58c34ae33700f3d9c4fb Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Tue, 13 Mar 2012 16:34:16 -0700
Subject: [PATCH 059/142] 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.
---
CHANGES | 12 ++++++++----
docs/config.rst | 9 ++++++++-
flask/app.py | 7 +++++++
flask/helpers.py | 14 +++++++++-----
flask/testsuite/blueprints.py | 14 ++++++++++++++
flask/testsuite/helpers.py | 13 +++++++++----
6 files changed, 55 insertions(+), 14 deletions(-)
diff --git a/CHANGES b/CHANGES
index 6b96be9eed..ee029adca5 100644
--- a/CHANGES
+++ b/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
diff --git a/docs/config.rst b/docs/config.rst
index 2f9d830727..cf3c6a4a55 100644
--- a/docs/config.rst
+++ b/docs/config.rst
@@ -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
diff --git a/flask/app.py b/flask/app.py
index f3d7efcb0c..0876ac64c1 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -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.
diff --git a/flask/helpers.py b/flask/helpers.py
index 9964792b05..52b0cebc9b 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -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,7 +656,7 @@ 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
@@ -660,14 +664,14 @@ def get_static_file_options(self, filename):
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
diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py
index 5bf81d9298..5f3d3ab35a 100644
--- a/flask/testsuite/blueprints.py
+++ b/flask/testsuite/blueprints.py
@@ -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')
diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py
index 423319939e..b4dd00ea60 100644
--- a/flask/testsuite/helpers.py
+++ b/flask/testsuite/helpers.py
@@ -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
From 73cb15ed2cb208381b31e7f868adfb4117cc803d Mon Sep 17 00:00:00 2001
From: iammookli
Date: Thu, 15 Mar 2012 18:20:17 -0700
Subject: [PATCH 060/142] Update docs/patterns/mongokit.rst
---
docs/patterns/mongokit.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/patterns/mongokit.rst b/docs/patterns/mongokit.rst
index a9c4eef532..b50cf4568a 100644
--- a/docs/patterns/mongokit.rst
+++ b/docs/patterns/mongokit.rst
@@ -141,4 +141,4 @@ These results are also dict-like objects:
u'admin@localhost'
For more information about MongoKit, head over to the
-`website `_.
+`website `_.
From fe9f5a47687cccaaaf13f160d747ce8b4c03bad9 Mon Sep 17 00:00:00 2001
From: jtsoi
Date: Fri, 16 Mar 2012 09:38:40 +0100
Subject: [PATCH 061/142] Added an example of how to configure debugging with
run_simple, it has to be enabled both for the Flask app and the Werkzeug
server.
---
docs/patterns/appdispatch.rst | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst
index 177ade2bc7..c48d3c280b 100644
--- a/docs/patterns/appdispatch.rst
+++ b/docs/patterns/appdispatch.rst
@@ -30,6 +30,23 @@ at :func:`werkzeug.serving.run_simple`::
Note that :func:`run_simple ` is not intended for
use in production. Use a :ref:`full-blown WSGI server `.
+In order to use the interactive debuggger, debugging must be enables both on
+the application and the simple server, here is the "hello world" example with debugging and
+:func:`run_simple ` :
+
+ from flask import Flask
+ from werkzeug.serving import run_simple
+
+ app = Flask(__name__)
+ app.debug = True
+
+ @app.route('/')
+ def hello_world():
+ return 'Hello World!'
+
+ if __name__ == '__main__':
+ run_simple('localhost', 5000, app, use_reloader=True, use_debugger=True, use_evalex=True)
+
Combining Applications
----------------------
From ee6ed491d3a783076c278e4b4390baf14e6f3321 Mon Sep 17 00:00:00 2001
From: Simon Sapin
Date: Mon, 19 Mar 2012 00:52:33 +0100
Subject: [PATCH 062/142] Have tox install simplejson for python 2.5
---
tox.ini | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tox.ini b/tox.ini
index 91c6d6641d..82c1588e5a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,3 +3,6 @@ envlist=py25,py26,py27,pypy
[testenv]
commands=python run-tests.py
+
+[testenv:py25]
+deps=simplejson
From bb99158c870a2d761f1349af02ef1decf0d10c7b Mon Sep 17 00:00:00 2001
From: Jon Parise
Date: Mon, 19 Mar 2012 22:33:43 -0700
Subject: [PATCH 063/142] Remove an unused iteration variable.
We can just iterate over the namespace dictionary's keys here. We
don't need its values.
---
flask/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flask/views.py b/flask/views.py
index 79d62992dd..5192c1c100 100644
--- a/flask/views.py
+++ b/flask/views.py
@@ -107,7 +107,7 @@ def __new__(cls, name, bases, d):
rv = type.__new__(cls, name, bases, d)
if 'methods' not in d:
methods = set(rv.methods or [])
- for key, value in d.iteritems():
+ for key in d:
if key in http_method_funcs:
methods.add(key.upper())
# if we have no method at all in there we don't want to
From c2661dd4bcb41e5a4c47709a8be7704870aba0de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Filip=20Risti=C4=87?=
Date: Tue, 20 Mar 2012 22:07:58 +0100
Subject: [PATCH 064/142] Update docs/patterns/packages.rst
---
docs/patterns/packages.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst
index 79fd2c58b8..1c7f9bd089 100644
--- a/docs/patterns/packages.rst
+++ b/docs/patterns/packages.rst
@@ -55,7 +55,7 @@ following quick checklist:
`__init__.py` file. That way each module can import it safely and the
`__name__` variable will resolve to the correct package.
2. all the view functions (the ones with a :meth:`~flask.Flask.route`
- decorator on top) have to be imported when in the `__init__.py` file.
+ decorator on top) have to be imported in the `__init__.py` file.
Not the object itself, but the module it is in. Import the view module
**after the application object is created**.
From b29834dac37a13f82019aa1c7e9d06622cf5790e Mon Sep 17 00:00:00 2001
From: Kamil Kisiel
Date: Sat, 24 Mar 2012 14:09:43 -0700
Subject: [PATCH 065/142] Fixed weird string quoting in setup.py
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 812b2c8a78..8169a51746 100644
--- a/setup.py
+++ b/setup.py
@@ -59,7 +59,7 @@ def run(self):
try:
import pyflakes.scripts.pyflakes as flakes
except ImportError:
- print "Audit requires PyFlakes installed in your system."""
+ print "Audit requires PyFlakes installed in your system."
sys.exit(-1)
warns = 0
From e08028de5521caf41fc11de8daec2795f1f51088 Mon Sep 17 00:00:00 2001
From: Simon Sapin
Date: Tue, 27 Mar 2012 17:08:55 +0300
Subject: [PATCH 066/142] pip vs. easy_install consistency
---
docs/installation.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/installation.rst b/docs/installation.rst
index 8e6a449765..791c99f1f5 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -94,7 +94,7 @@ System-Wide Installation
------------------------
This is possible as well, though I do not recommend it. Just run
-`easy_install` with root privileges::
+`pip` with root privileges::
$ sudo pip install Flask
From 35383ee83c568ce642ffef6733d8b91ebd206185 Mon Sep 17 00:00:00 2001
From: Pascal Hartig
Date: Wed, 28 Mar 2012 10:33:27 +0300
Subject: [PATCH 067/142] Removed triple-quotes from print statement in
setup.py
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 812b2c8a78..8169a51746 100644
--- a/setup.py
+++ b/setup.py
@@ -59,7 +59,7 @@ def run(self):
try:
import pyflakes.scripts.pyflakes as flakes
except ImportError:
- print "Audit requires PyFlakes installed in your system."""
+ print "Audit requires PyFlakes installed in your system."
sys.exit(-1)
warns = 0
From e76db15e26b76bdaed4649474f6509e383142e9c Mon Sep 17 00:00:00 2001
From: Sergey
Date: Sat, 31 Mar 2012 10:11:12 +0300
Subject: [PATCH 068/142] Update docs/quickstart.rst
---
docs/quickstart.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 0d8c5b738d..8f38aff5c9 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -193,7 +193,7 @@ The following converters exist:
with a trailing slash will produce a 404 "Not Found" error.
This behavior allows relative URLs to continue working if users access the
- page when they forget a trailing slash, consistent with how with how Apache
+ page when they forget a trailing slash, consistent with how Apache
and other servers work. Also, the URLs will stay unique, which helps search
engines avoid indexing the same page twice.
From 7e4b705b3c7124bc5bdd4051705488d8bb31eb5b Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Sun, 1 Apr 2012 10:54:00 -0400
Subject: [PATCH 069/142] Move others.rst to wsgi-standalone.rst.
---
docs/deploying/{others.rst => wsgi-standalone.rst} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename docs/deploying/{others.rst => wsgi-standalone.rst} (100%)
diff --git a/docs/deploying/others.rst b/docs/deploying/wsgi-standalone.rst
similarity index 100%
rename from docs/deploying/others.rst
rename to docs/deploying/wsgi-standalone.rst
From 976c9576bd7b41f955862448c9914774dc47d1cf Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Sun, 1 Apr 2012 10:54:27 -0400
Subject: [PATCH 070/142] Reorder deployment options.
---
docs/deploying/fastcgi.rst | 11 +++----
docs/deploying/index.rst | 6 ++--
docs/deploying/uwsgi.rst | 9 +++--
docs/deploying/wsgi-standalone.rst | 53 ++++++++++++++++--------------
4 files changed, 40 insertions(+), 39 deletions(-)
diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst
index 6dace1a8d9..ebd6856007 100644
--- a/docs/deploying/fastcgi.rst
+++ b/docs/deploying/fastcgi.rst
@@ -3,12 +3,11 @@
FastCGI
=======
-FastCGI is a deployment option on servers like `nginx`_, `lighttpd`_,
-and `cherokee`_; see :ref:`deploying-uwsgi` and
-:ref:`deploying-other-servers` for other options. To use your WSGI
-application with any of them you will need a FastCGI server first. The
-most popular one is `flup`_ which we will use for this guide. Make sure
-to have it installed to follow along.
+FastCGI is a deployment option on servers like `nginx`_, `lighttpd`_, and
+`cherokee`_; see :ref:`deploying-uwsgi` and :ref:`deploying-wsgi-standalone`
+for other options. To use your WSGI application with any of them you will need
+a FastCGI server first. The most popular one is `flup`_ which we will use for
+this guide. Make sure to have it installed to follow along.
.. admonition:: Watch Out
diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst
index 1b4189c3d8..bf78275d3e 100644
--- a/docs/deploying/index.rst
+++ b/docs/deploying/index.rst
@@ -20,7 +20,7 @@ For hosted options to get up and running quickly, see
:maxdepth: 2
mod_wsgi
- cgi
- fastcgi
+ wsgi-standalone
uwsgi
- others
+ fastcgi
+ cgi
diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst
index bdee15ba4a..b05fdeec22 100644
--- a/docs/deploying/uwsgi.rst
+++ b/docs/deploying/uwsgi.rst
@@ -4,11 +4,10 @@ uWSGI
=====
uWSGI is a deployment option on servers like `nginx`_, `lighttpd`_, and
-`cherokee`_; see :ref:`deploying-fastcgi` and
-:ref:`deploying-other-servers` for other options. To use your WSGI
-application with uWSGI protocol you will need a uWSGI server
-first. uWSGI is both a protocol and an application server; the
-application server can serve uWSGI, FastCGI, and HTTP protocols.
+`cherokee`_; see :ref:`deploying-fastcgi` and :ref:`deploying-wsgi-standalone`
+for other options. To use your WSGI application with uWSGI protocol you will
+need a uWSGI server first. uWSGI is both a protocol and an application server;
+the application server can serve uWSGI, FastCGI, and HTTP protocols.
The most popular uWSGI server is `uwsgi`_, which we will use for this
guide. Make sure to have it installed to follow along.
diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst
index 6f3e5cc6a0..4bb985d49a 100644
--- a/docs/deploying/wsgi-standalone.rst
+++ b/docs/deploying/wsgi-standalone.rst
@@ -1,11 +1,31 @@
-.. _deploying-other-servers:
+.. _deploying-wsgi-standalone:
-Other Servers
-=============
+Standalone WSGI Containers
+==========================
-There are popular servers written in Python that allow the execution of WSGI
-applications as well. These servers stand alone when they run; you can proxy
-to them from your web server.
+There are popular servers written in Python that contain WSGI applications and
+serve HTTP. These servers stand alone when they run; you can proxy to them
+from your web server. Note the section on :ref:`deploying-proxy-setups` if you
+run into issues.
+
+Gunicorn
+--------
+
+`Gunicorn`_ 'Green Unicorn' is a WSGI HTTP Server for UNIX. It's a pre-fork
+worker model ported from Ruby's Unicorn project. It supports both `eventlet`_
+and `greenlet`_. Running a Flask application on this server is quite simple::
+
+ gunicorn myproject:app
+
+`Gunicorn`_ provides many command-line options -- see ``gunicorn -h``.
+For example, to run a Flask application with 4 worker processes (``-w
+4``) binding to localhost port 4000 (``-b 127.0.0.1:4000``)::
+
+ gunicorn -w 4 -b 127.0.0.1:4000 myproject:app
+
+.. _Gunicorn: http://gunicorn.org/
+.. _eventlet: http://eventlet.net/
+.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html
Tornado
--------
@@ -14,7 +34,7 @@ Tornado
server and tools that power `FriendFeed`_. Because it is non-blocking and
uses epoll, it can handle thousands of simultaneous standing connections,
which means it is ideal for real-time web services. Integrating this
-service with Flask is a trivial task::
+service with Flask is straightforward::
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
@@ -46,24 +66,7 @@ event loop::
.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html
.. _libevent: http://monkey.org/~provos/libevent/
-Gunicorn
---------
-
-`Gunicorn`_ 'Green Unicorn' is a WSGI HTTP Server for UNIX. It's a pre-fork
-worker model ported from Ruby's Unicorn project. It supports both `eventlet`_
-and `greenlet`_. Running a Flask application on this server is quite simple::
-
- gunicorn myproject:app
-
-`Gunicorn`_ provides many command-line options -- see ``gunicorn -h``.
-For example, to run a Flask application with 4 worker processes (``-w
-4``) binding to localhost port 4000 (``-b 127.0.0.1:4000``)::
-
- gunicorn -w 4 -b 127.0.0.1:4000 myproject:app
-
-.. _Gunicorn: http://gunicorn.org/
-.. _eventlet: http://eventlet.net/
-.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html
+.. _deploying-proxy-setups:
Proxy Setups
------------
From 9a1d616706d251b19571d908282deadedd89869b Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Sun, 1 Apr 2012 10:57:44 -0400
Subject: [PATCH 071/142] Add simple proxying nginx config to docs.
Origin:
https://gist.github.com/2269917
---
docs/deploying/wsgi-standalone.rst | 34 ++++++++++++++++++++++++------
1 file changed, 28 insertions(+), 6 deletions(-)
diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst
index 4bb985d49a..422a934099 100644
--- a/docs/deploying/wsgi-standalone.rst
+++ b/docs/deploying/wsgi-standalone.rst
@@ -71,12 +71,34 @@ event loop::
Proxy Setups
------------
-If you deploy your application using one of these servers behind an HTTP
-proxy you will need to rewrite a few headers in order for the
-application to work. The two problematic values in the WSGI environment
-usually are `REMOTE_ADDR` and `HTTP_HOST`. Werkzeug ships a fixer that
-will solve some common setups, but you might want to write your own WSGI
-middleware for specific setups.
+If you deploy your application using one of these servers behind an HTTP proxy
+you will need to rewrite a few headers in order for the application to work.
+The two problematic values in the WSGI environment usually are `REMOTE_ADDR`
+and `HTTP_HOST`. You can configure your httpd to pass these headers, or you
+can fix them in middleware. Werkzeug ships a fixer that will solve some common
+setups, but you might want to write your own WSGI middleware for specific
+setups.
+
+Here's a simple nginx configuration which proxies to an application served on
+localhost at port 8000, setting appropriate headers::
+
+ server {
+ listen 80;
+
+ server_name _;
+
+ access_log /var/log/nginx/access.log;
+ error_log /var/log/nginx/error.log;
+
+ location / {
+ proxy_pass http://127.0.0.1:8000/;
+ proxy_redirect off;
+
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+ }
The most common setup invokes the host being set from `X-Forwarded-Host`
and the remote address from `X-Forwarded-For`::
From 9ab41edbd727d69d6936b866ea606c9b7e7bac8f Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Sun, 1 Apr 2012 11:19:51 -0400
Subject: [PATCH 072/142] Touch up proxying docs.
---
docs/deploying/wsgi-standalone.rst | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst
index 422a934099..7438581385 100644
--- a/docs/deploying/wsgi-standalone.rst
+++ b/docs/deploying/wsgi-standalone.rst
@@ -80,7 +80,9 @@ setups, but you might want to write your own WSGI middleware for specific
setups.
Here's a simple nginx configuration which proxies to an application served on
-localhost at port 8000, setting appropriate headers::
+localhost at port 8000, setting appropriate headers:
+
+.. sourcecode:: nginx
server {
listen 80;
@@ -100,15 +102,18 @@ localhost at port 8000, setting appropriate headers::
}
}
-The most common setup invokes the host being set from `X-Forwarded-Host`
-and the remote address from `X-Forwarded-For`::
+If your httpd is not providing these headers, the most common setup invokes the
+host being set from `X-Forwarded-Host` and the remote address from
+`X-Forwarded-For`::
from werkzeug.contrib.fixers import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)
-Please keep in mind that it is a security issue to use such a middleware
-in a non-proxy setup because it will blindly trust the incoming
-headers which might be forged by malicious clients.
+.. admonition:: Trusting Headers
+
+ Please keep in mind that it is a security issue to use such a middleware in
+ a non-proxy setup because it will blindly trust the incoming headers which
+ might be forged by malicious clients.
If you want to rewrite the headers from another header, you might want to
use a fixer like this::
From 0eb75b317bd62ece31875158fb31262ce7d05e69 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Sun, 1 Apr 2012 11:33:42 -0400
Subject: [PATCH 073/142] Add notes on mutable values & sessions.
Using notes in 8445f0d939dc3c4a2e722dc6dd4938d02bc2e094
---
CHANGES | 3 ++-
flask/helpers.py | 7 +++++++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/CHANGES b/CHANGES
index ee029adca5..bee0ab77cd 100644
--- a/CHANGES
+++ b/CHANGES
@@ -56,7 +56,8 @@ Relase date to be decided, codename to be chosen.
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.
-
+- Fixed an assumption in sessions implementation which could break message
+ flashing on sessions implementations which use external storage.
Version 0.8.1
-------------
diff --git a/flask/helpers.py b/flask/helpers.py
index e6fb4ae36b..294c5297d9 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -283,6 +283,13 @@ def flash(message, category='message'):
messages and ``'warning'`` for warnings. However any
kind of string can be used as category.
"""
+ # Original implementation:
+ #
+ # session.setdefault('_flashes', []).append((category, message))
+ #
+ # This assumed that changes made to mutable structures in the session are
+ # are always in sync with the sess on object, which is not true for session
+ # implementations that use external storage for keeping their keys/values.
flashes = session.get('_flashes', [])
flashes.append((category, message))
session['_flashes'] = flashes
From df772df24f22f7f0681a8d5b211dad764ce9c8a6 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Sun, 1 Apr 2012 11:40:37 -0400
Subject: [PATCH 074/142] Touch up run_simple doc, #446.
---
docs/patterns/appdispatch.rst | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst
index c48d3c280b..a2d1176fd0 100644
--- a/docs/patterns/appdispatch.rst
+++ b/docs/patterns/appdispatch.rst
@@ -30,9 +30,9 @@ at :func:`werkzeug.serving.run_simple`::
Note that :func:`run_simple ` is not intended for
use in production. Use a :ref:`full-blown WSGI server `.
-In order to use the interactive debuggger, debugging must be enables both on
-the application and the simple server, here is the "hello world" example with debugging and
-:func:`run_simple ` :
+In order to use the interactive debuggger, debugging must be enabled both on
+the application and the simple server, here is the "hello world" example with
+debugging and :func:`run_simple `::
from flask import Flask
from werkzeug.serving import run_simple
@@ -45,7 +45,8 @@ the application and the simple server, here is the "hello world" example with de
return 'Hello World!'
if __name__ == '__main__':
- run_simple('localhost', 5000, app, use_reloader=True, use_debugger=True, use_evalex=True)
+ run_simple('localhost', 5000, app,
+ use_reloader=True, use_debugger=True, use_evalex=True)
Combining Applications
From 91efb90fb3d580bad438c353bdfe4d604051a3a4 Mon Sep 17 00:00:00 2001
From: jfinkels
Date: Sun, 1 Apr 2012 13:03:22 -0300
Subject: [PATCH 075/142] Removed extra blank line to fix ReST formatted
documentation string in wrappers.py. Should have gone with pull request #439.
---
flask/wrappers.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/flask/wrappers.py b/flask/wrappers.py
index 541d26ef3c..3ee718ffb8 100644
--- a/flask/wrappers.py
+++ b/flask/wrappers.py
@@ -118,7 +118,6 @@ def on_json_loading_failed(self, e):
this server could not understand."}
.. versionchanged:: 0.9
-
Return a :class:`JSONBadRequest` instead of a
:class:`~werkzeug.exceptions.BadRequest` by default.
From f46f7155b27a741081fc13fa7fb1db53e54f5683 Mon Sep 17 00:00:00 2001
From: Kenneth Reitz
Date: Sun, 1 Apr 2012 20:57:50 -0400
Subject: [PATCH 076/142] 2012
---
LICENSE | 2 +-
docs/conf.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/LICENSE b/LICENSE
index 5d2693890d..dc01ee1a47 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2010 by Armin Ronacher and contributors. See AUTHORS
+Copyright (c) 2012 by Armin Ronacher and contributors. See AUTHORS
for more details.
Some rights reserved.
diff --git a/docs/conf.py b/docs/conf.py
index 16d7e6709f..30df71479d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -43,7 +43,7 @@
# General information about the project.
project = u'Flask'
-copyright = u'2010, Armin Ronacher'
+copyright = u'2012, Armin Ronacher'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
From b16c988f1e7de5f0ec9dae11817b9109de59355d Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Tue, 3 Apr 2012 20:55:58 -0400
Subject: [PATCH 077/142] Fix static endpoint name mention in quickstart.
---
docs/quickstart.rst | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 8f38aff5c9..8497f082d0 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -333,8 +333,7 @@ configured to serve them for you, but during development Flask can do that
as well. Just create a folder called `static` in your package or next to
your module and it will be available at `/static` on the application.
-To generate URLs that part of the URL, use the special ``'static'`` URL
-name::
+To generate URLs for static files, use the special ``'static'`` endpoint name::
url_for('static', filename='style.css')
From 492ef06bff569d6037fa43e561b203fe444b60d5 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Wed, 4 Apr 2012 11:39:07 -0400
Subject: [PATCH 078/142] Clarify use of context-locals with signals.
---
docs/signals.rst | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/docs/signals.rst b/docs/signals.rst
index 2d3878f75a..df92dce7e4 100644
--- a/docs/signals.rst
+++ b/docs/signals.rst
@@ -131,6 +131,8 @@ debugging. You can access the name of the signal with the
missing blinker installations, you can do so by using the
:class:`flask.signals.Namespace` class.
+.. _signals-sending:
+
Sending Signals
---------------
@@ -156,6 +158,17 @@ function, you can pass ``current_app._get_current_object()`` as sender.
that :data:`~flask.current_app` is a proxy and not the real application
object.
+
+Signals and Flask's Request Context
+-----------------------------------
+
+Signals fully support :ref:`reqcontext` when receiving signals. Context-local
+variables are consistently available between :data:`~flask.request_started` and
+:data:`~flask.request_finished`, so you can rely on :class:`flask.g` and others
+as needed. Note the limitations described in :ref:`signals-sending` and the
+:data:`~flask.request_tearing_down` signal.
+
+
Decorator Based Signal Subscriptions
------------------------------------
From f07199009c463ed5eaab7b2cacd785d46f87699d Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Wed, 4 Apr 2012 11:41:40 -0400
Subject: [PATCH 079/142] Fix reqcontext ref in signals doc.
---
docs/signals.rst | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/signals.rst b/docs/signals.rst
index df92dce7e4..a4cd4157d3 100644
--- a/docs/signals.rst
+++ b/docs/signals.rst
@@ -162,11 +162,11 @@ function, you can pass ``current_app._get_current_object()`` as sender.
Signals and Flask's Request Context
-----------------------------------
-Signals fully support :ref:`reqcontext` when receiving signals. Context-local
-variables are consistently available between :data:`~flask.request_started` and
-:data:`~flask.request_finished`, so you can rely on :class:`flask.g` and others
-as needed. Note the limitations described in :ref:`signals-sending` and the
-:data:`~flask.request_tearing_down` signal.
+Signals fully support :ref:`request-context` when receiving signals.
+Context-local variables are consistently available between
+:data:`~flask.request_started` and :data:`~flask.request_finished`, so you can
+rely on :class:`flask.g` and others as needed. Note the limitations described
+in :ref:`signals-sending` and the :data:`~flask.request_tearing_down` signal.
Decorator Based Signal Subscriptions
From 9c48387072128c32dde06dc9a6e812195f18012d Mon Sep 17 00:00:00 2001
From: Dmitry Shevchenko
Date: Sun, 8 Apr 2012 18:09:12 -0500
Subject: [PATCH 080/142] Removed unneeded print statements form mongokit
pattern doc
---
docs/patterns/mongokit.rst | 3 ---
1 file changed, 3 deletions(-)
diff --git a/docs/patterns/mongokit.rst b/docs/patterns/mongokit.rst
index b50cf4568a..b4b6fc011c 100644
--- a/docs/patterns/mongokit.rst
+++ b/docs/patterns/mongokit.rst
@@ -122,9 +122,6 @@ collection first, this is somewhat the same as a table in the SQL world.
>>> user = {'name': u'admin', 'email': u'admin@localhost'}
>>> collection.insert(user)
-print list(collection.find())
-print collection.find_one({'name': u'admin'})
-
MongoKit will automatically commit for us.
To query your database, you use the collection directly:
From a1305973bfef18a341224ad4f748c35d77a64cdb Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 14:19:13 +0100
Subject: [PATCH 081/142] Fixed a typo in a comment
---
flask/ctx.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flask/ctx.py b/flask/ctx.py
index 47ac0cc106..f9558d2e96 100644
--- a/flask/ctx.py
+++ b/flask/ctx.py
@@ -95,7 +95,7 @@ def __init__(self, app, environ):
self.match_request()
- # XXX: Support for deprecated functionality. This is doing away with
+ # XXX: Support for deprecated functionality. This is going away with
# Flask 1.0
blueprint = self.request.blueprint
if blueprint is not None:
From 47288231fe8f9c6b2c413d50160c32c3884d5785 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 14:34:12 +0100
Subject: [PATCH 082/142] Implemented a separate application context.
---
flask/app.py | 17 ++++++++++++++-
flask/ctx.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++-
flask/globals.py | 10 ++++++++-
3 files changed, 78 insertions(+), 3 deletions(-)
diff --git a/flask/app.py b/flask/app.py
index 15e432dee7..38d31f955c 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -28,7 +28,7 @@
find_package
from .wrappers import Request, Response
from .config import ConfigAttribute, Config
-from .ctx import RequestContext
+from .ctx import RequestContext, AppContext
from .globals import _request_ctx_stack, request
from .sessions import SecureCookieSessionInterface
from .module import blueprint_is_module
@@ -1458,6 +1458,21 @@ def do_teardown_request(self):
return rv
request_tearing_down.send(self)
+ def app_context(self):
+ """Binds the application only. For as long as the application is bound
+ to the current context the :data:`flask.current_app` points to that
+ application. An application context is automatically created when a
+ request context is pushed if necessary.
+
+ Example usage::
+
+ with app.app_context():
+ ...
+
+ .. versionadded:: 0.9
+ """
+ return AppContext(self)
+
def request_context(self, environ):
"""Creates a :class:`~flask.ctx.RequestContext` from the given
environment and binds it to the current context. This must be used in
diff --git a/flask/ctx.py b/flask/ctx.py
index f9558d2e96..7bfd598efd 100644
--- a/flask/ctx.py
+++ b/flask/ctx.py
@@ -11,7 +11,7 @@
from werkzeug.exceptions import HTTPException
-from .globals import _request_ctx_stack
+from .globals import _request_ctx_stack, _app_ctx_stack
from .module import blueprint_is_module
@@ -19,6 +19,14 @@ class _RequestGlobals(object):
pass
+def _push_app_if_necessary(app):
+ top = _app_ctx_stack.top
+ if top is None or top.app != app:
+ ctx = app.app_context()
+ ctx.push()
+ return ctx
+
+
def has_request_context():
"""If you have code that wants to test if a request context is there or
not this function can be used. For instance, you may want to take advantage
@@ -51,6 +59,36 @@ def __init__(self, username, remote_addr=None):
return _request_ctx_stack.top is not None
+class AppContext(object):
+ """The application context binds an application object implicitly
+ to the current thread or greenlet, similar to how the
+ :class:`RequestContext` binds request information. The application
+ context is also implicitly created if a request context is created
+ but the application is not on top of the individual application
+ context.
+ """
+
+ def __init__(self, app):
+ self.app = app
+
+ def push(self):
+ """Binds the app context to the current context."""
+ _app_ctx_stack.push(self)
+
+ def pop(self):
+ """Pops the app context."""
+ rv = _app_ctx_stack.pop()
+ assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
+ % (rv, self)
+
+ def __enter__(self):
+ self.push()
+ return self
+
+ def __exit__(self, exc_type, exc_value, tb):
+ self.pop()
+
+
class RequestContext(object):
"""The request context contains all request relevant information. It is
created at the beginning of the request and pushed to the
@@ -93,6 +131,11 @@ def __init__(self, app, environ):
# is pushed the preserved context is popped.
self.preserved = False
+ # Indicates if pushing this request context also triggered the pushing
+ # of an application context. If it implicitly pushed an application
+ # context, it will be stored there
+ self._pushed_application_context = None
+
self.match_request()
# XXX: Support for deprecated functionality. This is going away with
@@ -130,6 +173,10 @@ def push(self):
if top is not None and top.preserved:
top.pop()
+ # Before we push the request context we have to ensure that there
+ # is an application context.
+ self._pushed_application_context = _push_app_if_necessary(self.app)
+
_request_ctx_stack.push(self)
# Open the session at the moment that the request context is
@@ -154,6 +201,11 @@ def pop(self):
# so that we don't require the GC to be active.
rv.request.environ['werkzeug.request'] = None
+ # Get rid of the app as well if necessary.
+ if self._pushed_application_context:
+ self._pushed_application_context.pop()
+ self._pushed_application_context = None
+
def __enter__(self):
self.push()
return self
diff --git a/flask/globals.py b/flask/globals.py
index 16580d16d2..f6d6248537 100644
--- a/flask/globals.py
+++ b/flask/globals.py
@@ -20,9 +20,17 @@ def _lookup_object(name):
return getattr(top, name)
+def _find_app():
+ top = _app_ctx_stack.top
+ if top is None:
+ raise RuntimeError('working outside of application context')
+ return top.app
+
+
# context locals
_request_ctx_stack = LocalStack()
-current_app = LocalProxy(partial(_lookup_object, 'app'))
+_app_ctx_stack = LocalStack()
+current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_object, 'request'))
session = LocalProxy(partial(_lookup_object, 'session'))
g = LocalProxy(partial(_lookup_object, 'g'))
From 307d1bc4e51fc35282f2d504b9df3359604b8f8a Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 15:04:35 +0100
Subject: [PATCH 083/142] Added support for basic URL generation without
request contexts.
---
flask/app.py | 19 ++++++++++++---
flask/ctx.py | 1 +
flask/helpers.py | 50 +++++++++++++++++++++++++--------------
flask/testsuite/appctx.py | 38 +++++++++++++++++++++++++++++
4 files changed, 87 insertions(+), 21 deletions(-)
create mode 100644 flask/testsuite/appctx.py
diff --git a/flask/app.py b/flask/app.py
index 38d31f955c..215bf0ad9a 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -250,7 +250,8 @@ class Flask(_PackageBoundObject):
'SESSION_COOKIE_SECURE': False,
'MAX_CONTENT_LENGTH': None,
'TRAP_BAD_REQUEST_ERRORS': False,
- 'TRAP_HTTP_EXCEPTIONS': False
+ 'TRAP_HTTP_EXCEPTIONS': False,
+ 'PREFERRED_URL_SCHEME': 'http'
})
#: The rule object to use for URL rules created. This is used by
@@ -1370,9 +1371,21 @@ def create_url_adapter(self, request):
so the request is passed explicitly.
.. versionadded:: 0.6
+
+ .. versionchanged:: 0.9
+ This can now also be called without a request object when the
+ UR adapter is created for the application context.
"""
- return self.url_map.bind_to_environ(request.environ,
- server_name=self.config['SERVER_NAME'])
+ if request is not None:
+ return self.url_map.bind_to_environ(request.environ,
+ server_name=self.config['SERVER_NAME'])
+ # We need at the very least the server name to be set for this
+ # to work.
+ if self.config['SERVER_NAME'] is not None:
+ return self.url_map.bind(
+ self.config['SERVER_NAME'],
+ script_name=self.config['APPLICATION_ROOT'] or '/',
+ url_scheme=self.config['PREFERRED_URL_SCHEME'])
def inject_url_defaults(self, endpoint, values):
"""Injects the URL defaults for the given endpoint directly into
diff --git a/flask/ctx.py b/flask/ctx.py
index 7bfd598efd..a9088cf4ad 100644
--- a/flask/ctx.py
+++ b/flask/ctx.py
@@ -70,6 +70,7 @@ class AppContext(object):
def __init__(self, app):
self.app = app
+ self.url_adapter = app.create_url_adapter(None)
def push(self):
"""Binds the app context to the current context."""
diff --git a/flask/helpers.py b/flask/helpers.py
index 25250d26c4..26be5e3057 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -50,7 +50,8 @@
from jinja2 import FileSystemLoader
-from .globals import session, _request_ctx_stack, current_app, request
+from .globals import session, _request_ctx_stack, _app_ctx_stack, \
+ current_app, request
def _assert_have_json():
@@ -197,27 +198,40 @@ def url_for(endpoint, **values):
:param _anchor: if provided this is added as anchor to the URL.
:param _method: if provided this explicitly specifies an HTTP method.
"""
- ctx = _request_ctx_stack.top
- blueprint_name = request.blueprint
- if not ctx.request._is_old_module:
- if endpoint[:1] == '.':
- if blueprint_name is not None:
- endpoint = blueprint_name + endpoint
- else:
+ appctx = _app_ctx_stack.top
+ reqctx = _request_ctx_stack.top
+
+ # If request specific information is available we have some extra
+ # features that support "relative" urls.
+ if reqctx is not None:
+ url_adapter = reqctx.url_adapter
+ blueprint_name = request.blueprint
+ if not reqctx.request._is_old_module:
+ if endpoint[:1] == '.':
+ if blueprint_name is not None:
+ endpoint = blueprint_name + endpoint
+ else:
+ endpoint = endpoint[1:]
+ else:
+ # TODO: get rid of this deprecated functionality in 1.0
+ if '.' not in endpoint:
+ if blueprint_name is not None:
+ endpoint = blueprint_name + '.' + endpoint
+ elif endpoint.startswith('.'):
endpoint = endpoint[1:]
+ external = values.pop('_external', False)
+
+ # Otherwise go with the url adapter from the appctx and make
+ # the urls external by default.
else:
- # TODO: get rid of this deprecated functionality in 1.0
- if '.' not in endpoint:
- if blueprint_name is not None:
- endpoint = blueprint_name + '.' + endpoint
- elif endpoint.startswith('.'):
- endpoint = endpoint[1:]
- external = values.pop('_external', False)
+ url_adapter = appctx.url_adapter
+ external = values.pop('_external', True)
+
anchor = values.pop('_anchor', None)
method = values.pop('_method', None)
- ctx.app.inject_url_defaults(endpoint, values)
- rv = ctx.url_adapter.build(endpoint, values, method=method,
- force_external=external)
+ appctx.app.inject_url_defaults(endpoint, values)
+ rv = url_adapter.build(endpoint, values, method=method,
+ force_external=external)
if anchor is not None:
rv += '#' + url_quote(anchor)
return rv
diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py
new file mode 100644
index 0000000000..2c19804774
--- /dev/null
+++ b/flask/testsuite/appctx.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite.appctx
+ ~~~~~~~~~~~~~~~~~~~~~~
+
+ Tests the application context.
+
+ :copyright: (c) 2012 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import with_statement
+
+import flask
+import unittest
+from flask.testsuite import FlaskTestCase
+
+
+class AppContextTestCase(FlaskTestCase):
+
+ def test_basic_support(self):
+ app = flask.Flask(__name__)
+ app.config['SERVER_NAME'] = 'localhost'
+ app.config['PREFERRED_URL_SCHEME'] = 'https'
+
+ @app.route('/')
+ def index():
+ pass
+
+ with app.app_context():
+ rv = flask.url_for('index')
+ self.assert_equal(rv, 'https://localhost/')
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(AppContextTestCase))
+ return suite
From f8f2e2dff481e55fa9ae7cc3f70f36d61bcf56d7 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 15:16:09 +0100
Subject: [PATCH 084/142] Added more tests for the new stack behavior.
---
flask/__init__.py | 3 ++-
flask/helpers.py | 8 ++++++++
flask/testsuite/appctx.py | 24 +++++++++++++++++++++++-
3 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/flask/__init__.py b/flask/__init__.py
index 54bfedda47..f35ef328e2 100644
--- a/flask/__init__.py
+++ b/flask/__init__.py
@@ -23,7 +23,8 @@
from .helpers import url_for, jsonify, json_available, flash, \
send_file, send_from_directory, get_flashed_messages, \
get_template_attribute, make_response, safe_join
-from .globals import current_app, g, request, session, _request_ctx_stack
+from .globals import current_app, g, request, session, _request_ctx_stack, \
+ _app_ctx_stack
from .ctx import has_request_context
from .module import Module
from .blueprints import Blueprint
diff --git a/flask/helpers.py b/flask/helpers.py
index 26be5e3057..b86ce15857 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -200,6 +200,9 @@ def url_for(endpoint, **values):
"""
appctx = _app_ctx_stack.top
reqctx = _request_ctx_stack.top
+ if appctx is None:
+ raise RuntimeError('Attempted to generate a URL with the application '
+ 'context being pushed. This has to be executed ')
# If request specific information is available we have some extra
# features that support "relative" urls.
@@ -225,6 +228,11 @@ def url_for(endpoint, **values):
# the urls external by default.
else:
url_adapter = appctx.url_adapter
+ if url_adapter is None:
+ raise RuntimeError('Application was not able to create a URL '
+ 'adapter for request independent URL generation. '
+ 'You might be able to fix this by setting '
+ 'the SERVER_NAME config variable.')
external = values.pop('_external', True)
anchor = values.pop('_anchor', None)
diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py
index 2c19804774..c60dbc67c6 100644
--- a/flask/testsuite/appctx.py
+++ b/flask/testsuite/appctx.py
@@ -18,7 +18,7 @@
class AppContextTestCase(FlaskTestCase):
- def test_basic_support(self):
+ def test_basic_url_generation(self):
app = flask.Flask(__name__)
app.config['SERVER_NAME'] = 'localhost'
app.config['PREFERRED_URL_SCHEME'] = 'https'
@@ -31,6 +31,28 @@ def index():
rv = flask.url_for('index')
self.assert_equal(rv, 'https://localhost/')
+ def test_url_generation_requires_server_name(self):
+ app = flask.Flask(__name__)
+ with app.app_context():
+ with self.assert_raises(RuntimeError):
+ flask.url_for('index')
+
+ def test_url_generation_without_context_fails(self):
+ with self.assert_raises(RuntimeError):
+ flask.url_for('index')
+
+ def test_request_context_means_app_context(self):
+ app = flask.Flask(__name__)
+ with app.test_request_context():
+ self.assert_equal(flask.current_app._get_current_object(), app)
+ self.assert_equal(flask._app_ctx_stack.top, None)
+
+ def test_app_context_provides_current_app(self):
+ app = flask.Flask(__name__)
+ with app.app_context():
+ self.assert_equal(flask.current_app._get_current_object(), app)
+ self.assert_equal(flask._app_ctx_stack.top, None)
+
def suite():
suite = unittest.TestSuite()
From 0207e90155abe937568727e4e9eca949b8247cd5 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 15:22:36 +0100
Subject: [PATCH 085/142] Updated docs for the app context.
---
CHANGES | 3 +++
docs/api.rst | 16 +++++++++++++++-
flask/ctx.py | 10 ++++++++++
3 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/CHANGES b/CHANGES
index 8311e2e758..5436a85736 100644
--- a/CHANGES
+++ b/CHANGES
@@ -45,6 +45,9 @@ Relase date to be decided, codename to be chosen.
- The :meth:`flask.render_template` method now accepts a either an iterable of
template names or a single template name. Previously, it only accepted a
single template name. On an iterable, the first template found is rendered.
+- Added :meth:`flask.Flask.app_context` which works very similar to the
+ request context but only provides access to the current application. This
+ also adds support for URL generation without an active request context.
Version 0.8.1
diff --git a/docs/api.rst b/docs/api.rst
index ec7e4f6381..78ed2d8df5 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -265,12 +265,16 @@ Useful Functions and Classes
Points to the application handling the request. This is useful for
extensions that want to support multiple applications running side
- by side.
+ by side. This is powered by the application context and not by the
+ request context, so you can change the value of this proxy by
+ using the :meth:`~flask.Flask.app_context` method.
This is a proxy. See :ref:`notes-on-proxies` for more information.
.. autofunction:: has_request_context
+.. autofunction:: has_app_context
+
.. autofunction:: url_for
.. function:: abort(code)
@@ -412,6 +416,16 @@ Useful Internals
if ctx is not None:
return ctx.session
+.. autoclass:: flask.ctx.AppContext
+ :members:
+
+.. data:: _app_ctx_stack
+
+ Works similar to the request context but only binds the application.
+ This is mainly there for extensions to store data.
+
+ .. versionadded:: 0.9
+
.. autoclass:: flask.blueprints.BlueprintSetupState
:members:
diff --git a/flask/ctx.py b/flask/ctx.py
index a9088cf4ad..887b259851 100644
--- a/flask/ctx.py
+++ b/flask/ctx.py
@@ -59,6 +59,16 @@ def __init__(self, username, remote_addr=None):
return _request_ctx_stack.top is not None
+def has_app_context():
+ """Worksl ike :func:`has_request_context` but for the application
+ context. You can also just do a boolean check on the
+ :data:`current_app` object instead.
+
+ .. versionadded:: 0.9
+ """
+ return _app_ctx_stack.top is not None
+
+
class AppContext(object):
"""The application context binds an application object implicitly
to the current thread or greenlet, similar to how the
From ab110d8fe573c92f0f607a05cf31549399b98c1a Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 15:24:43 +0100
Subject: [PATCH 086/142] Documented config changes
---
docs/config.rst | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/docs/config.rst b/docs/config.rst
index 2f9d830727..a5d2a9f0d2 100644
--- a/docs/config.rst
+++ b/docs/config.rst
@@ -95,7 +95,10 @@ The following configuration values are used internally by Flask:
``'myapp.dev:5000'``) Note that
localhost does not support subdomains so
setting this to “localhost” does not
- help.
+ help. Setting a ``SERVER_NAME`` also
+ by default enables URL generation
+ without a request context but with an
+ application context.
``APPLICATION_ROOT`` If the application does not occupy
a whole domain or subdomain this can
be set to the path where the application
@@ -126,6 +129,9 @@ The following configuration values are used internally by Flask:
used to debug those situations. If this
config is set to ``True`` you will get
a regular traceback instead.
+``PREFERRED_URL_SCHEME`` The URL scheme that should be used for
+ URL generation if no URL scheme is
+ available. This defaults to ``http``.
================================= =========================================
.. admonition:: More on ``SERVER_NAME``
@@ -165,6 +171,9 @@ The following configuration values are used internally by Flask:
``SESSION_COOKIE_PATH``, ``SESSION_COOKIE_HTTPONLY``,
``SESSION_COOKIE_SECURE``
+.. versionadded:: 0.9
+ ``PREFERRED_URL_SCHEME``
+
Configuring from Files
----------------------
From cf1641e5beec1ec11f418fa6e775fc44b9410180 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 15:56:33 +0100
Subject: [PATCH 087/142] Changed the implementation of returning tuples from
functions
---
CHANGES | 2 ++
docs/quickstart.rst | 7 +++--
docs/upgrading.rst | 15 +++++++++++
flask/app.py | 55 ++++++++++++++++++++++++----------------
flask/testsuite/basic.py | 15 +++++------
5 files changed, 61 insertions(+), 33 deletions(-)
diff --git a/CHANGES b/CHANGES
index bb03d088f3..cb7b751e00 100644
--- a/CHANGES
+++ b/CHANGES
@@ -63,6 +63,8 @@ Relase date to be decided, codename to be chosen.
the `get_send_file_options` hook is used.
- Fixed an assumption in sessions implementation which could break message
flashing on sessions implementations which use external storage.
+- Changed the behavior of tuple return values from functions. They are no
+ longer arguments to the response object, they now have a defined meaning.
Version 0.8.1
-------------
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 8497f082d0..f7b6ee02da 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -674,8 +674,11 @@ converting return values into response objects is as follows:
returned from the view.
2. If it's a string, a response object is created with that data and the
default parameters.
-3. If a tuple is returned the response object is created by passing the
- tuple as arguments to the response object's constructor.
+3. If a tuple is returned the items in the tuple can provide extra
+ information. Such tuples have to be in the form ``(response, status,
+ headers)`` where at least one item has to be in the tuple. The
+ `status` value will override the status code and `headers` can be a
+ list or dictionary of additional header values.
4. If none of that works, Flask will assume the return value is a
valid WSGI application and convert that into a response object.
diff --git a/docs/upgrading.rst b/docs/upgrading.rst
index 0ba46c13d5..ab00624ebf 100644
--- a/docs/upgrading.rst
+++ b/docs/upgrading.rst
@@ -19,6 +19,21 @@ installation, make sure to pass it the ``-U`` parameter::
$ easy_install -U Flask
+Version 0.9
+-----------
+
+The behavior of returning tuples from a function was simplified. If you
+return a tuple it no longer defines the arguments for the response object
+you're creating, it's now always a tuple in the form ``(response, status,
+headers)`` where at least one item has to be provided. If you depend on
+the old behavior, you can add it easily by subclassing Flask::
+
+ class TraditionalFlask(Flask):
+ def make_response(self, rv):
+ if isinstance(rv, tuple):
+ return self.response_class(*rv)
+ return Flask.make_response(self, rv)
+
Version 0.8
-----------
diff --git a/flask/app.py b/flask/app.py
index e16b35bfb3..a0ffed6682 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -1354,37 +1354,48 @@ def make_response(self, rv):
string as body
:class:`unicode` a response object is created with the
string encoded to utf-8 as body
- :class:`tuple` the response object is created with the
- contents of the tuple as arguments
a WSGI function the function is called as WSGI application
and buffered as response object
+ :class:`tuple` A tuple in the form ``(response, status,
+ headers)`` where `response` is any of the
+ types defined here, `status` is a string
+ or an integer and `headers` is a list of
+ a dictionary with header values.
======================= ===========================================
:param rv: the return value from the view function
+
+ .. versionchanged:: 0.9
+ Previously a tuple was interpreted as the arguments for the
+ response object.
"""
+ status = headers = None
+ if isinstance(rv, tuple):
+ rv, status, headers = rv + (None,) * (3 - len(rv))
+
if rv is None:
raise ValueError('View function did not return a response')
- if isinstance(rv, self.response_class):
- return rv
- if isinstance(rv, basestring):
- return self.response_class(rv)
- if isinstance(rv, tuple):
- if len(rv) > 0 and isinstance(rv[0], self.response_class):
- original = rv[0]
- new_response = self.response_class('', *rv[1:])
- if len(rv) < 3:
- # The args for the response class are
- # response=None, status=None, headers=None,
- # mimetype=None, content_type=None, ...
- # so if there's at least 3 elements the rv
- # tuple contains header information so the
- # headers from rv[0] "win."
- new_response.headers = original.headers
- new_response.response = original.response
- return new_response
+
+ if not isinstance(rv, self.response_class):
+ # When we create a response object directly, we let the constructor
+ # set the headers and status. We do this because there can be
+ # some extra logic involved when creating these objects with
+ # specific values (like defualt content type selection).
+ if isinstance(rv, basestring):
+ rv = self.response_class(rv, headers=headers, status=status)
+ headers = status = None
+ else:
+ rv = self.response_class.force_type(rv, request.environ)
+
+ if status is not None:
+ if isinstance(status, basestring):
+ rv.status = status
else:
- return self.response_class(*rv)
- return self.response_class.force_type(rv, request.environ)
+ rv.status_code = status
+ if headers:
+ rv.headers.extend(headers)
+
+ return rv
def create_url_adapter(self, request):
"""Creates a URL adapter for the given request. The URL adapter
diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py
index 41efb19617..0a4b1d9c84 100644
--- a/flask/testsuite/basic.py
+++ b/flask/testsuite/basic.py
@@ -631,7 +631,10 @@ def from_string():
return u'Hällo Wörld'.encode('utf-8')
@app.route('/args')
def from_tuple():
- return 'Meh', 400, {'X-Foo': 'Testing'}, 'text/plain'
+ return 'Meh', 400, {
+ 'X-Foo': 'Testing',
+ 'Content-Type': 'text/plain; charset=utf-8'
+ }
c = app.test_client()
self.assert_equal(c.get('/unicode').data, u'Hällo Wörld'.encode('utf-8'))
self.assert_equal(c.get('/string').data, u'Hällo Wörld'.encode('utf-8'))
@@ -677,16 +680,10 @@ def test_make_response_with_response_instance(self):
rv = flask.make_response(
flask.Response('', headers={'Content-Type': 'text/html'}),
- 400, None, 'application/json')
- self.assertEqual(rv.status_code, 400)
- self.assertEqual(rv.headers['Content-Type'], 'application/json')
-
- rv = flask.make_response(
- flask.Response('', mimetype='application/json'),
- 400, {'Content-Type': 'text/html'})
+ 400, [('X-Foo', 'bar')])
self.assertEqual(rv.status_code, 400)
self.assertEqual(rv.headers['Content-Type'], 'text/html')
-
+ self.assertEqual(rv.headers['X-Foo'], 'bar')
def test_url_generation(self):
app = flask.Flask(__name__)
From 34bbd3100bbfb9a406a417a9fe4e8944aa87c629 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 16:11:35 +0100
Subject: [PATCH 088/142] Fixed a failing testcase
---
flask/helpers.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/flask/helpers.py b/flask/helpers.py
index 01f4081e54..1be2daf728 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -726,7 +726,9 @@ def get_send_file_options(self, filename):
.. versionadded:: 0.9
"""
- return {}
+ options = {}
+ options['cache_timeout'] = current_app.config['SEND_FILE_MAX_AGE_DEFAULT']
+ return options
def send_static_file(self, filename):
"""Function used internally to send static files from the static
From 9bed20c07c40a163077b936f740a4e96a6213688 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 17:29:00 +0100
Subject: [PATCH 089/142] Added documentation for appcontext and teardown
handlers
---
docs/api.rst | 14 +++++-
docs/appcontext.rst | 88 +++++++++++++++++++++++++++++++++
docs/contents.rst.inc | 1 +
docs/extensiondev.rst | 100 ++++++++++++++++++++++++++------------
docs/reqcontext.rst | 26 ++--------
docs/signals.rst | 23 +++++++++
flask/app.py | 68 +++++++++++++++++++++++---
flask/ctx.py | 18 +++++--
flask/signals.py | 1 +
flask/testsuite/appctx.py | 12 +++++
10 files changed, 287 insertions(+), 64 deletions(-)
create mode 100644 docs/appcontext.rst
diff --git a/docs/api.rst b/docs/api.rst
index 9733287022..b09bcad515 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -469,8 +469,18 @@ Signals
.. data:: request_tearing_down
This signal is sent when the application is tearing down the request.
- This is always called, even if an error happened. No arguments are
- provided.
+ This is always called, even if an error happened. An `exc` keyword
+ argument is passed with the exception that caused the teardown.
+
+ .. versionchanged:: 0.9
+ The `exc` parameter was added.
+
+.. data:: appcontext_tearing_down
+
+ This signal is sent when the application is tearing down the
+ application context. This is always called, even if an error happened.
+ An `exc` keyword argument is passed with the exception that caused the
+ teardown.
.. currentmodule:: None
diff --git a/docs/appcontext.rst b/docs/appcontext.rst
new file mode 100644
index 0000000000..c331ffa51c
--- /dev/null
+++ b/docs/appcontext.rst
@@ -0,0 +1,88 @@
+.. _app_context:
+
+The Application Context
+=======================
+
+.. versionadded:: 0.9
+
+One of the design ideas behind Flask is that there are two different
+“states” in which code is executed. The application setup state in which
+the application implicitly is on the module level. It starts when the
+:class:`Flask` object is instantiated, and it implicitly ends when the
+first request comes in. While the application is in this state a few
+assumptions are true:
+
+- the programmer can modify the application object safely.
+- no request handling happened so far
+- you have to have a reference to the application object in order to
+ modify it, there is no magic proxy that can give you a reference to
+ the application object you're currently creating or modifying.
+
+On the contrast, during request handling, a couple of other rules exist:
+
+- while a request is active, the context local objects
+ (:data:`flask.request` and others) point to the current request.
+- any code can get hold of these objects at any time.
+
+There is a third state which is sitting in between a little bit.
+Sometimes you are dealing with an application in a way that is similar to
+how you interact with applications during request handling just that there
+is no request active. Consider for instance that you're sitting in an
+interactive Python shell and interacting with the application, or a
+command line application.
+
+The application context is what powers the :data:`~flask.current_app`
+context local.
+
+Purpose of the Application Context
+----------------------------------
+
+The main reason for the application's context existance is that in the
+past a bunch of functionality was attached to the request context in lack
+of a better solution. Since one of the pillar's of Flask's design is that
+you can have more than one application in the same Python process.
+
+So how does the code find the “right” application? In the past we
+recommended passing applications around explicitly, but that caused issues
+with libraries that were not designed with that in mind for libraries for
+which it was too inconvenient to make this work.
+
+A common workaround for that problem was to use the
+:data:`~flask.current_app` proxy later on, which was bound to the current
+request's application reference. Since however creating such a request
+context is an unnecessarily expensive operation in case there is no
+request around, the application context was introduced.
+
+Creating an Application Context
+-------------------------------
+
+To make an application context there are two ways. The first one is the
+implicit one: whenever a request context is pushed, an application context
+will be created alongside if this is necessary. As a result of that, you
+can ignore the existance of the application context unless you need it.
+
+The second way is the explicit way using the
+:meth:`~flask.Flask.app_context` method::
+
+ from flask import Flask, current_app
+
+ app = Flask(__name__)
+ with app.app_context():
+ # within this block, current_app points to app.
+ print current_app.name
+
+The application context is also used by the :func:`~flask.url_for`
+function in case a ``SERVER_NAME`` was configured. This allows you to
+generate URLs even in the absence of a request.
+
+Locality of the Context
+-----------------------
+
+The application context is created and destroyed as necessary. It never
+moves between threads and it will not be shared between requests. As such
+it is the perfect place to store database connection information and other
+things. The internal stack object is called :data:`flask._app_ctx_stack`.
+Extensions are free to store additional information on the topmost level,
+assuming they pick a sufficiently unique name.
+
+For more information about that, see :ref:`extension-dev`.
diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc
index a8ebc0d722..a1893c4886 100644
--- a/docs/contents.rst.inc
+++ b/docs/contents.rst.inc
@@ -18,6 +18,7 @@ instructions for web development with Flask.
config
signals
views
+ appcontext
reqcontext
blueprints
extensions
diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst
index 60aa6c3785..2511cec7da 100644
--- a/docs/extensiondev.rst
+++ b/docs/extensiondev.rst
@@ -1,3 +1,5 @@
+.. _extension-dev:
+
Flask Extension Development
===========================
@@ -152,6 +154,11 @@ What to use depends on what you have in mind. For the SQLite 3 extension
we will use the class-based approach because it will provide users with an
object that handles opening and closing database connections.
+What's important about classes is that they encourage to be shared around
+on module level. In that case, the object itself must not under any
+circumstances store any application specific state and must be shareable
+between different application.
+
The Extension Code
------------------
@@ -159,7 +166,13 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste::
import sqlite3
- from flask import _request_ctx_stack
+ # Find the stack on which we want to store the database connection.
+ # Starting with Flask 0.9, the _app_ctx_stack is the correct one,
+ # before that we need to use the _request_ctx_stack.
+ try:
+ from flask import _app_ctx_stack as stack
+ except ImportError:
+ from flask import _request_ctx_stack as stack
class SQLite3(object):
@@ -172,26 +185,28 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste::
self.app = None
def init_app(self, app):
- self.app = app
- self.app.config.setdefault('SQLITE3_DATABASE', ':memory:')
- self.app.teardown_request(self.teardown_request)
- self.app.before_request(self.before_request)
+ app.config.setdefault('SQLITE3_DATABASE', ':memory:')
+ # Use the newstyle teardown_appcontext if it's available,
+ # otherwise fall back to the request context
+ if hasattr(app, 'teardown_appcontext'):
+ app.teardown_appcontext(self.teardown)
+ else:
+ app.teardown_request(self.teardown)
def connect(self):
return sqlite3.connect(self.app.config['SQLITE3_DATABASE'])
- def before_request(self):
- ctx = _request_ctx_stack.top
- ctx.sqlite3_db = self.connect()
-
- def teardown_request(self, exception):
- ctx = _request_ctx_stack.top
- ctx.sqlite3_db.close()
+ def teardown(self, exception):
+ ctx = stack.top
+ if hasattr(ctx, 'sqlite3_db'):
+ ctx.sqlite3_db.close()
@property
def connection(self):
- ctx = _request_ctx_stack.top
+ ctx = stack.top
if ctx is not None:
+ if not hasattr(ctx, 'sqlite3_db'):
+ ctx.sqlite3_db = self.connect()
return ctx.sqlite3_db
@@ -204,14 +219,19 @@ So here's what these lines of code do:
factory pattern for creating applications. The ``init_app`` will set the
configuration for the database, defaulting to an in memory database if
no configuration is supplied. In addition, the ``init_app`` method attaches
- ``before_request`` and ``teardown_request`` handlers.
+ the ``teardown`` handler. It will try to use the newstyle app context
+ handler and if it does not exist, falls back to the request context
+ one.
3. Next, we define a ``connect`` method that opens a database connection.
-4. Then we set up the request handlers we bound to the app above. Note here
- that we're attaching our database connection to the top request context via
- ``_request_ctx_stack.top``. Extensions should use the top context and not the
- ``g`` object to store things like database connections.
-5. Finally, we add a ``connection`` property that simplifies access to the context's
- database.
+4. Finally, we add a ``connection`` property that on first access opens
+ the database connection and stores it on the context.
+
+ Note here that we're attaching our database connection to the top
+ application context via ``_app_ctx_stack.top``. Extensions should use
+ the top context for storing their own information with a sufficiently
+ complex name. Note that we're falling back to the
+ ``_request_ctx_stack.top`` if the application is using an older
+ version of Flask that does not support it.
So why did we decide on a class-based approach here? Because using our
extension looks something like this::
@@ -241,19 +261,38 @@ for creating apps::
Keep in mind that supporting this factory pattern for creating apps is required
for approved flask extensions (described below).
+.. admonition:: Note on ``init_app``
-Using _request_ctx_stack
-------------------------
+ As you noticed, ``init_app`` does not assign ``app`` to ``self``. This
+ is intentional! Class based Flask extensions must only store the
+ application on the object when the application was passed to the
+ constructor. This tells the extension: I am not interested in using
+ multiple applications.
-In the example above, before every request, a ``sqlite3_db`` variable is assigned
-to ``_request_ctx_stack.top``. In a view function, this variable is accessible
-using the ``connection`` property of ``SQLite3``. During the teardown of a
-request, the ``sqlite3_db`` connection is closed. By using this pattern, the
-*same* connection to the sqlite3 database is accessible to anything that needs it
-for the duration of the request.
+ When the extension needs to find the current application and it does
+ not have a reference to it, it must either use the
+ :data:`~flask.current_app` context local or change the API in a way
+ that you can pass the application explicitly.
-End-Of-Request Behavior
------------------------
+
+Using _app_ctx_stack
+--------------------
+
+In the example above, before every request, a ``sqlite3_db`` variable is
+assigned to ``_app_ctx_stack.top``. In a view function, this variable is
+accessible using the ``connection`` property of ``SQLite3``. During the
+teardown of a request, the ``sqlite3_db`` connection is closed. By using
+this pattern, the *same* connection to the sqlite3 database is accessible
+to anything that needs it for the duration of the request.
+
+If the :data:`~flask._app_ctx_stack` does not exist because the user uses
+an old version of Flask, it is recommended to fall back to
+:data:`~flask._request_ctx_stack` which is bound to a request.
+
+Teardown Behavior
+-----------------
+
+*This is only relevant if you want to support Flask 0.6 and older*
Due to the change in Flask 0.7 regarding functions that are run at the end
of the request your extension will have to be extra careful there if it
@@ -270,7 +309,6 @@ pattern is a good way to support both::
else:
app.after_request(close_connection)
-
Strictly speaking the above code is wrong, because teardown functions are
passed the exception and typically don't return anything. However because
the return value is discarded this will just work assuming that the code
diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst
index 0249b88e7b..327afe6cc1 100644
--- a/docs/reqcontext.rst
+++ b/docs/reqcontext.rst
@@ -6,27 +6,7 @@ The Request Context
This document describes the behavior in Flask 0.7 which is mostly in line
with the old behavior but has some small, subtle differences.
-One of the design ideas behind Flask is that there are two different
-“states” in which code is executed. The application setup state in which
-the application implicitly is on the module level. It starts when the
-:class:`Flask` object is instantiated, and it implicitly ends when the
-first request comes in. While the application is in this state a few
-assumptions are true:
-
-- the programmer can modify the application object safely.
-- no request handling happened so far
-- you have to have a reference to the application object in order to
- modify it, there is no magic proxy that can give you a reference to
- the application object you're currently creating or modifying.
-
-On the contrast, during request handling, a couple of other rules exist:
-
-- while a request is active, the context local objects
- (:data:`flask.request` and others) point to the current request.
-- any code can get hold of these objects at any time.
-
-The magic that makes this works is internally referred in Flask as the
-“request context”.
+It is recommended that you read the :api:`app-context` chapter first.
Diving into Context Locals
--------------------------
@@ -107,6 +87,10 @@ the very top, :meth:`~flask.ctx.RequestContext.pop` removes it from the
stack again. On popping the application's
:func:`~flask.Flask.teardown_request` functions are also executed.
+Another thing of note is that the request context will automatically also
+create an :ref:`application context ` when it's pushed and
+there is no application context for that application so far.
+
.. _callbacks-and-errors:
Callbacks and Errors
diff --git a/docs/signals.rst b/docs/signals.rst
index a4cd4157d3..959c53bd2e 100644
--- a/docs/signals.rst
+++ b/docs/signals.rst
@@ -268,4 +268,27 @@ The following signals exist in Flask:
from flask import request_tearing_down
request_tearing_down.connect(close_db_connection, app)
+ As of Flask 0.9, this will also be passed an `exc` keyword argument
+ that has a reference to the exception that caused the teardown if
+ there was one.
+
+.. data:: flask.appcontext_tearing_down
+ :noindex:
+
+ This signal is sent when the app context is tearing down. This is always
+ called, even if an exception is caused. Currently functions listening
+ to this signal are called after the regular teardown handlers, but this
+ is not something you can rely on.
+
+ Example subscriber::
+
+ def close_db_connection(sender, **extra):
+ session.close()
+
+ from flask import request_tearing_down
+ appcontext_tearing_down.connect(close_db_connection, app)
+
+ This will also be passed an `exc` keyword argument that has a reference
+ to the exception that caused the teardown if there was one.
+
.. _blinker: http://pypi.python.org/pypi/blinker
diff --git a/flask/app.py b/flask/app.py
index a0ffed6682..8460f47607 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -35,7 +35,7 @@
from .templating import DispatchingJinjaLoader, Environment, \
_default_template_ctx_processor
from .signals import request_started, request_finished, got_request_exception, \
- request_tearing_down
+ request_tearing_down, appcontext_tearing_down
# a lock used for logger initialization
_logger_lock = Lock()
@@ -364,6 +364,14 @@ def __init__(self, import_name, static_path=None, static_url_path=None,
#: .. versionadded:: 0.7
self.teardown_request_funcs = {}
+ #: A list of functions that are called when the application context
+ #: is destroyed. Since the application context is also torn down
+ #: if the request ends this is the place to store code that disconnects
+ #: from databases.
+ #:
+ #: .. versionadded:: 0.9
+ self.teardown_appcontext_funcs = []
+
#: A dictionary with lists of functions that can be used as URL
#: value processor functions. Whenever a URL is built these functions
#: are called to modify the dictionary of values in place. The key
@@ -1106,10 +1114,42 @@ def teardown_request(self, f):
that they will fail. If they do execute code that might fail they
will have to surround the execution of these code by try/except
statements and log ocurring errors.
+
+ When a teardown function was called because of a exception it will
+ be passed an error object.
"""
self.teardown_request_funcs.setdefault(None, []).append(f)
return f
+ @setupmethod
+ def teardown_appcontext(self, f):
+ """Registers a function to be called when the application context
+ ends. These functions are typically also called when the request
+ context is popped.
+
+ Example::
+
+ ctx = app.app_context()
+ ctx.push()
+ ...
+ ctx.pop()
+
+ When ``ctx.pop()`` is executed in the above example, the teardown
+ functions are called just before the app context moves from the
+ stack of active contexts. This becomes relevant if you are using
+ such constructs in tests.
+
+ Since a request context typically also manages an application
+ context it would also be called when you pop a request context.
+
+ When a teardown function was called because of an exception it will
+ be passed an error object.
+
+ .. versionadded:: 0.9
+ """
+ self.teardown_appcontext_funcs.append(f)
+ return f
+
@setupmethod
def context_processor(self, f):
"""Registers a template context processor function."""
@@ -1485,23 +1525,39 @@ def process_response(self, response):
self.save_session(ctx.session, response)
return response
- def do_teardown_request(self):
+ def do_teardown_request(self, exc=None):
"""Called after the actual request dispatching and will
call every as :meth:`teardown_request` decorated function. This is
not actually called by the :class:`Flask` object itself but is always
triggered when the request context is popped. That way we have a
tighter control over certain resources under testing environments.
+
+ .. versionchanged:: 0.9
+ Added the `exc` argument. Previously this was always using the
+ current exception information.
"""
+ if exc is None:
+ exc = sys.exc_info()[1]
funcs = reversed(self.teardown_request_funcs.get(None, ()))
bp = _request_ctx_stack.top.request.blueprint
if bp is not None and bp in self.teardown_request_funcs:
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
- exc = sys.exc_info()[1]
for func in funcs:
rv = func(exc)
- if rv is not None:
- return rv
- request_tearing_down.send(self)
+ request_tearing_down.send(self, exc=exc)
+
+ def do_teardown_appcontext(self, exc=None):
+ """Called when an application context is popped. This works pretty
+ much the same as :meth:`do_teardown_request` but for the application
+ context.
+
+ .. versionadded:: 0.9
+ """
+ if exc is None:
+ exc = sys.exc_info()[1]
+ for func in reversed(self.teardown_appcontext_funcs):
+ func(exc)
+ appcontext_tearing_down.send(self, exc=exc)
def app_context(self):
"""Binds the application only. For as long as the application is bound
diff --git a/flask/ctx.py b/flask/ctx.py
index 887b259851..0ed5ea436e 100644
--- a/flask/ctx.py
+++ b/flask/ctx.py
@@ -9,6 +9,8 @@
:license: BSD, see LICENSE for more details.
"""
+import sys
+
from werkzeug.exceptions import HTTPException
from .globals import _request_ctx_stack, _app_ctx_stack
@@ -86,8 +88,11 @@ def push(self):
"""Binds the app context to the current context."""
_app_ctx_stack.push(self)
- def pop(self):
+ def pop(self, exc=None):
"""Pops the app context."""
+ if exc is None:
+ exc = sys.exc_info()[1]
+ self.app.do_teardown_appcontext(exc)
rv = _app_ctx_stack.pop()
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
% (rv, self)
@@ -197,13 +202,18 @@ def push(self):
if self.session is None:
self.session = self.app.make_null_session()
- def pop(self):
+ def pop(self, exc=None):
"""Pops the request context and unbinds it by doing that. This will
also trigger the execution of functions registered by the
:meth:`~flask.Flask.teardown_request` decorator.
+
+ .. versionchanged:: 0.9
+ Added the `exc` argument.
"""
self.preserved = False
- self.app.do_teardown_request()
+ if exc is None:
+ exc = sys.exc_info()[1]
+ self.app.do_teardown_request(exc)
rv = _request_ctx_stack.pop()
assert rv is self, 'Popped wrong request context. (%r instead of %r)' \
% (rv, self)
@@ -231,7 +241,7 @@ def __exit__(self, exc_type, exc_value, tb):
(tb is not None and self.app.preserve_context_on_exception):
self.preserved = True
else:
- self.pop()
+ self.pop(exc_value)
def __repr__(self):
return '<%s \'%s\' [%s] of %s>' % (
diff --git a/flask/signals.py b/flask/signals.py
index eeb763d475..78a77bd556 100644
--- a/flask/signals.py
+++ b/flask/signals.py
@@ -49,3 +49,4 @@ def _fail(self, *args, **kwargs):
request_finished = _signals.signal('request-finished')
request_tearing_down = _signals.signal('request-tearing-down')
got_request_exception = _signals.signal('got-request-exception')
+appcontext_tearing_down = _signals.signal('appcontext-tearing-down')
diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py
index c60dbc67c6..a4ad479b6f 100644
--- a/flask/testsuite/appctx.py
+++ b/flask/testsuite/appctx.py
@@ -53,6 +53,18 @@ def test_app_context_provides_current_app(self):
self.assert_equal(flask.current_app._get_current_object(), app)
self.assert_equal(flask._app_ctx_stack.top, None)
+ def test_app_tearing_down(self):
+ cleanup_stuff = []
+ app = flask.Flask(__name__)
+ @app.teardown_appcontext
+ def cleanup(exception):
+ cleanup_stuff.append(exception)
+
+ with app.app_context():
+ pass
+
+ self.assert_equal(cleanup_stuff, [None])
+
def suite():
suite = unittest.TestSuite()
From cb54c462b809e36d12af571fa36affb4af3f7e96 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 17:32:37 +0100
Subject: [PATCH 090/142] Pass exc explicitly to the inner context.
---
flask/ctx.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flask/ctx.py b/flask/ctx.py
index 0ed5ea436e..413ca884fb 100644
--- a/flask/ctx.py
+++ b/flask/ctx.py
@@ -224,7 +224,7 @@ def pop(self, exc=None):
# Get rid of the app as well if necessary.
if self._pushed_application_context:
- self._pushed_application_context.pop()
+ self._pushed_application_context.pop(exc)
self._pushed_application_context = None
def __enter__(self):
From 32f845ea75ce57429aa383463be6654b2af06983 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 17:33:14 +0100
Subject: [PATCH 091/142] Added an example for using the db connection without
the request
---
docs/extensiondev.rst | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst
index 2511cec7da..16a354e682 100644
--- a/docs/extensiondev.rst
+++ b/docs/extensiondev.rst
@@ -250,6 +250,17 @@ You can then use the database from views like this::
cur = db.connection.cursor()
cur.execute(...)
+Likewise if you are outside of a request but you are using Flask 0.9 or
+later with the app context support, you can use the database in the same
+way::
+
+ with app.app_context():
+ cur = db.connection.cursor()
+ cur.execute(...)
+
+At the end of the `with` block the teardown handles will be executed
+automatically.
+
Additionally, the ``init_app`` method is used to support the factory pattern
for creating apps::
From 52f9cefbcd6df23f75bf93804a9e84c038536fe8 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 17:35:16 +0100
Subject: [PATCH 092/142] More documentation updates for 0.9
---
docs/extensiondev.rst | 4 +++-
docs/upgrading.rst | 6 ++++++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst
index 16a354e682..86c7c72160 100644
--- a/docs/extensiondev.rst
+++ b/docs/extensiondev.rst
@@ -224,7 +224,9 @@ So here's what these lines of code do:
one.
3. Next, we define a ``connect`` method that opens a database connection.
4. Finally, we add a ``connection`` property that on first access opens
- the database connection and stores it on the context.
+ the database connection and stores it on the context. This is also
+ the recommended way to handling resources: fetch resources lazily the
+ first time they are used.
Note here that we're attaching our database connection to the top
application context via ``_app_ctx_stack.top``. Extensions should use
diff --git a/docs/upgrading.rst b/docs/upgrading.rst
index ab00624ebf..5955e5521d 100644
--- a/docs/upgrading.rst
+++ b/docs/upgrading.rst
@@ -34,6 +34,12 @@ the old behavior, you can add it easily by subclassing Flask::
return self.response_class(*rv)
return Flask.make_response(self, rv)
+If you have an extension that was using :data:`~flask._request_ctx_stack`
+before, please consider changing to :data:`~flask._app_ctx_stack` if it
+makes sense for your extension. This will for example be the case for
+extensions that connect to databases. This will allow your users to
+easier use your extension with more complex use cases outside of requests.
+
Version 0.8
-----------
From d26af4fd6dd71793cf6373c1c18c82349494e0aa Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 17:38:08 +0100
Subject: [PATCH 093/142] Fixed some smaller things in the docs
---
docs/appcontext.rst | 2 +-
docs/reqcontext.rst | 2 +-
flask/__init__.py | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/appcontext.rst b/docs/appcontext.rst
index c331ffa51c..e9e1ad8f25 100644
--- a/docs/appcontext.rst
+++ b/docs/appcontext.rst
@@ -1,4 +1,4 @@
-.. _app_context:
+.. _app-context:
The Application Context
=======================
diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst
index 327afe6cc1..4da5acd869 100644
--- a/docs/reqcontext.rst
+++ b/docs/reqcontext.rst
@@ -6,7 +6,7 @@ The Request Context
This document describes the behavior in Flask 0.7 which is mostly in line
with the old behavior but has some small, subtle differences.
-It is recommended that you read the :api:`app-context` chapter first.
+It is recommended that you read the :ref:`app-context` chapter first.
Diving into Context Locals
--------------------------
diff --git a/flask/__init__.py b/flask/__init__.py
index f35ef328e2..b91f9395d9 100644
--- a/flask/__init__.py
+++ b/flask/__init__.py
@@ -25,7 +25,7 @@
get_template_attribute, make_response, safe_join
from .globals import current_app, g, request, session, _request_ctx_stack, \
_app_ctx_stack
-from .ctx import has_request_context
+from .ctx import has_request_context, has_app_context
from .module import Module
from .blueprints import Blueprint
from .templating import render_template, render_template_string
From bcd00e5070caf6f5ff7639d3758f5595bc5507a1 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Mon, 9 Apr 2012 17:56:37 +0100
Subject: [PATCH 094/142] Fixed a typo
---
flask/ctx.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flask/ctx.py b/flask/ctx.py
index 413ca884fb..16b0350310 100644
--- a/flask/ctx.py
+++ b/flask/ctx.py
@@ -62,7 +62,7 @@ def __init__(self, username, remote_addr=None):
def has_app_context():
- """Worksl ike :func:`has_request_context` but for the application
+ """Works like :func:`has_request_context` but for the application
context. You can also just do a boolean check on the
:data:`current_app` object instead.
From 9d09632dbfc0c08ab1a5290fbf282aad7d13e2f8 Mon Sep 17 00:00:00 2001
From: Sean Vieira
Date: Mon, 9 Apr 2012 17:17:52 -0300
Subject: [PATCH 095/142] Fix spelling.
---
flask/blueprints.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flask/blueprints.py b/flask/blueprints.py
index d81d3c7350..9c55702891 100644
--- a/flask/blueprints.py
+++ b/flask/blueprints.py
@@ -25,7 +25,7 @@ def __init__(self, blueprint, app, options, first_registration):
#: a reference to the current application
self.app = app
- #: a reference to the blurprint that created this setup state.
+ #: a reference to the blueprint that created this setup state.
self.blueprint = blueprint
#: a dictionary with all options that were passed to the
From acb61ae57b6fe9a82aaa9804c0e4bba8c2441746 Mon Sep 17 00:00:00 2001
From: Paul McMillan
Date: Tue, 10 Apr 2012 13:14:38 -0700
Subject: [PATCH 096/142] Minor docs fix.
---
docs/quickstart.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index f7b6ee02da..daaecb23c4 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -744,7 +744,7 @@ sessions work::
@app.route('/logout')
def logout():
- # remove the username from the session if its there
+ # remove the username from the session if it's there
session.pop('username', None)
return redirect(url_for('index'))
From a2eb5efcd8be834ca7e30e6392fa3a2067ad3a55 Mon Sep 17 00:00:00 2001
From: Florent Xicluna
Date: Thu, 12 Apr 2012 19:14:52 +0300
Subject: [PATCH 097/142] few typos
---
scripts/flaskext_compat.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/scripts/flaskext_compat.py b/scripts/flaskext_compat.py
index 40c8c6b58f..2f58ccc42d 100644
--- a/scripts/flaskext_compat.py
+++ b/scripts/flaskext_compat.py
@@ -58,7 +58,7 @@ def load_module(self, fullname):
except ImportError:
exc_type, exc_value, tb = sys.exc_info()
# since we only establish the entry in sys.modules at the
- # very this seems to be redundant, but if recursive imports
+ # end this seems to be redundant, but if recursive imports
# happen we will call into the move import a second time.
# On the second invocation we still don't have an entry for
# fullname in sys.modules, but we will end up with the same
@@ -71,7 +71,7 @@ def load_module(self, fullname):
# If it's an important traceback we reraise it, otherwise
# we swallow it and try the next choice. The skipped frame
- # is the one from __import__ above which we don't care about
+ # is the one from __import__ above which we don't care about.
if self.is_important_traceback(realname, tb):
raise exc_type, exc_value, tb.tb_next
continue
@@ -106,7 +106,7 @@ def is_important_frame(self, important_module, tb):
if module_name == important_module:
return True
- # Some python verisons will will clean up modules so early that the
+ # Some python versions will clean up modules so early that the
# module name at that point is no longer set. Try guessing from
# the filename then.
filename = os.path.abspath(tb.tb_frame.f_code.co_filename)
From ffbab00cd1c9ef89ab795b14b187334766556be7 Mon Sep 17 00:00:00 2001
From: Natan
Date: Tue, 17 Apr 2012 19:28:28 -0700
Subject: [PATCH 098/142] Rectified rampant 'roule'.
---
docs/api.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/api.rst b/docs/api.rst
index b09bcad515..c329852e29 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -586,7 +586,7 @@ with the route parameter the view function is defined with the decorator
instead of the `view_func` parameter.
=============== ==========================================================
-`rule` the URL roule as string
+`rule` the URL rule as string
`endpoint` the endpoint for the registered URL rule. Flask itself
assumes that the name of the view function is the name
of the endpoint if not explicitly stated.
From 0d2ffc094b45e717439c679255f4aecd018e24d0 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Wed, 18 Apr 2012 15:44:07 -0400
Subject: [PATCH 099/142] Use 'venv' consistently for virtualenv directory.
Pointed out by tri on #pocoo.
---
docs/installation.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/installation.rst b/docs/installation.rst
index 791c99f1f5..8dcae5a772 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -61,7 +61,7 @@ information about how to do that. Once you have it installed, run the same
commands as above, but without the `sudo` prefix.
Once you have virtualenv installed, just fire up a shell and create
-your own environment. I usually create a project folder and an `env`
+your own environment. I usually create a project folder and a `venv`
folder within::
$ mkdir myproject
From b885edf81013f05161525db1eaf4e64fc6f5c2c4 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Wed, 18 Apr 2012 15:54:04 -0400
Subject: [PATCH 100/142] Fix typo pointed out by tri on #pocoo.
---
docs/patterns/appdispatch.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst
index a2d1176fd0..4f6dc18b78 100644
--- a/docs/patterns/appdispatch.rst
+++ b/docs/patterns/appdispatch.rst
@@ -90,7 +90,7 @@ the dynamic application creation.
The perfect level for abstraction in that regard is the WSGI layer. You
write your own WSGI application that looks at the request that comes and
-and delegates it to your Flask application. If that application does not
+delegates it to your Flask application. If that application does not
exist yet, it is dynamically created and remembered::
from threading import Lock
From 10c34e6652fac704c9c77a47d4853896f3030e34 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Thu, 19 Apr 2012 11:50:08 -0400
Subject: [PATCH 101/142] Reword 0.9 upgrade doc, thanks to plaes on #pocoo.
---
docs/upgrading.rst | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/docs/upgrading.rst b/docs/upgrading.rst
index 5955e5521d..7226d60e3b 100644
--- a/docs/upgrading.rst
+++ b/docs/upgrading.rst
@@ -34,11 +34,12 @@ the old behavior, you can add it easily by subclassing Flask::
return self.response_class(*rv)
return Flask.make_response(self, rv)
-If you have an extension that was using :data:`~flask._request_ctx_stack`
-before, please consider changing to :data:`~flask._app_ctx_stack` if it
-makes sense for your extension. This will for example be the case for
-extensions that connect to databases. This will allow your users to
-easier use your extension with more complex use cases outside of requests.
+If you maintain an extension that was using :data:`~flask._request_ctx_stack`
+before, please consider changing to :data:`~flask._app_ctx_stack` if it makes
+sense for your extension. For instance, the app context stack makes sense for
+extensions which connect to databases. Using the app context stack instead of
+the request stack will make extensions more readily handle use cases outside of
+requests.
Version 0.8
-----------
From a3cb2a33829ee517530d30cd920e5d652b358086 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Thu, 19 Apr 2012 11:51:38 -0400
Subject: [PATCH 102/142] Use American English for "behavior" in docs.
Prompted by plaes on #pocoo, mitsuhiko confirmed to use American English.
---
CHANGES | 8 ++++----
docs/design.rst | 2 +-
docs/extensiondev.rst | 4 ++--
docs/htmlfaq.rst | 2 +-
docs/patterns/jquery.rst | 2 +-
docs/patterns/mongokit.rst | 2 +-
docs/quickstart.rst | 2 +-
docs/templating.rst | 2 +-
flask/app.py | 2 +-
flask/helpers.py | 10 +++++-----
10 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/CHANGES b/CHANGES
index cb7b751e00..83c1f4fe4b 100644
--- a/CHANGES
+++ b/CHANGES
@@ -153,7 +153,7 @@ Released on June 28th 2011, codename Grappa
- Added :meth:`~flask.Flask.make_default_options_response`
which can be used by subclasses to alter the default
- behaviour for `OPTIONS` responses.
+ behavior for `OPTIONS` responses.
- Unbound locals now raise a proper :exc:`RuntimeError` instead
of an :exc:`AttributeError`.
- Mimetype guessing and etag support based on file objects is now
@@ -163,7 +163,7 @@ Released on June 28th 2011, codename Grappa
- Static file handling for modules now requires the name of the
static folder to be supplied explicitly. The previous autodetection
was not reliable and caused issues on Google's App Engine. Until
- 1.0 the old behaviour will continue to work but issue dependency
+ 1.0 the old behavior will continue to work but issue dependency
warnings.
- fixed a problem for Flask to run on jython.
- added a `PROPAGATE_EXCEPTIONS` configuration variable that can be
@@ -281,14 +281,14 @@ Released on July 6th 2010, codename Calvados
the session cookie cross-subdomain wide.
- autoescaping is no longer active for all templates. Instead it
is only active for ``.html``, ``.htm``, ``.xml`` and ``.xhtml``.
- Inside templates this behaviour can be changed with the
+ Inside templates this behavior can be changed with the
``autoescape`` tag.
- refactored Flask internally. It now consists of more than a
single file.
- :func:`flask.send_file` now emits etags and has the ability to
do conditional responses builtin.
- (temporarily) dropped support for zipped applications. This was a
- rarely used feature and led to some confusing behaviour.
+ rarely used feature and led to some confusing behavior.
- added support for per-package template and static-file directories.
- removed support for `create_jinja_loader` which is no longer used
in 0.5 due to the improved module support.
diff --git a/docs/design.rst b/docs/design.rst
index 6ca363a664..cc247f3b15 100644
--- a/docs/design.rst
+++ b/docs/design.rst
@@ -48,7 +48,7 @@ allocated will be freed again.
Another thing that becomes possible when you have an explicit object lying
around in your code is that you can subclass the base class
-(:class:`~flask.Flask`) to alter specific behaviour. This would not be
+(:class:`~flask.Flask`) to alter specific behavior. This would not be
possible without hacks if the object were created ahead of time for you
based on a class that is not exposed to you.
diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst
index 86c7c72160..59ca76c57e 100644
--- a/docs/extensiondev.rst
+++ b/docs/extensiondev.rst
@@ -40,7 +40,7 @@ that it works with multiple Flask application instances at once. This is
a requirement because many people will use patterns like the
:ref:`app-factories` pattern to create their application as needed to aid
unittests and to support multiple configurations. Because of that it is
-crucial that your application supports that kind of behaviour.
+crucial that your application supports that kind of behavior.
Most importantly the extension must be shipped with a `setup.py` file and
registered on PyPI. Also the development checkout link should work so
@@ -145,7 +145,7 @@ initialization functions:
classes:
Classes work mostly like initialization functions but can later be
- used to further change the behaviour. For an example look at how the
+ used to further change the behavior. For an example look at how the
`OAuth extension`_ works: there is an `OAuth` object that provides
some helper functions like `OAuth.remote_app` to create a reference to
a remote application that uses OAuth.
diff --git a/docs/htmlfaq.rst b/docs/htmlfaq.rst
index 1da25f3d4f..b16f4cd5dc 100644
--- a/docs/htmlfaq.rst
+++ b/docs/htmlfaq.rst
@@ -52,7 +52,7 @@ Development of the HTML5 specification was started in 2004 under the name
"Web Applications 1.0" by the Web Hypertext Application Technology Working
Group, or WHATWG (which was formed by the major browser vendors Apple,
Mozilla, and Opera) with the goal of writing a new and improved HTML
-specification, based on existing browser behaviour instead of unrealistic
+specification, based on existing browser behavior instead of unrealistic
and backwards-incompatible specifications.
For example, in HTML4 ``
Date: Fri, 20 Apr 2012 09:07:58 -0400
Subject: [PATCH 103/142] Add detailed Apache httpd fastcgi configuration.
---
docs/deploying/fastcgi.rst | 60 ++++++++++++++++++++++++++++++++++++--
1 file changed, 57 insertions(+), 3 deletions(-)
diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst
index ebd6856007..91824af08b 100644
--- a/docs/deploying/fastcgi.rst
+++ b/docs/deploying/fastcgi.rst
@@ -51,6 +51,61 @@ can execute it:
# chmod +x /var/www/yourapplication/yourapplication.fcgi
+Configuring Apache
+------------------
+
+The example above is good enough for a basic Apache deployment but your `.fcgi` file will appear in your application URL e.g. www.example.com/yourapplication.fcgi/news/. There are few ways to resolve it. A preferable way is to use Apache ScriptAlias configuration directive::
+
+
+ ServerName example.com
+ ScriptAlias / /path/to/yourapplication.fcgi/
+
+
+Another way is to use a custom WSGI middleware. For example on a shared web hosting::
+
+ .htaccess
+
+
+ AddHandler fcgid-script .fcgi
+
+ SetHandler fcgid-script
+ Options +FollowSymLinks +ExecCGI
+
+
+
+
+ Options +FollowSymlinks
+ RewriteEngine On
+ RewriteBase /
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^(.*)$ yourapplication.fcgi/$1 [QSA,L]
+
+
+ yourapplication.fcgi
+
+ #!/usr/bin/python
+ #: optional path to your local python site-packages folder
+ import sys
+ sys.path.insert(0, '/lib/python2.6/site-packages')
+
+ from flup.server.fcgi import WSGIServer
+ from yourapplication import app
+
+ class ScriptNameStripper(object):
+ to_strip = '/yourapplication.fcgi'
+
+ def __init__(self, app):
+ self.app = app
+
+ def __call__(self, environ, start_response):
+ environ['SCRIPT_NAME'] = ''
+ return self.app(environ, start_response)
+
+ app = ScriptNameStripper(app)
+
+ if __name__ == '__main__':
+ WSGIServer(app).run()
+
Configuring lighttpd
--------------------
@@ -84,7 +139,6 @@ root. Also, see the Lighty docs for more information on `FastCGI and
Python `_
(note that explicitly passing a socket to run() is no longer necessary).
-
Configuring nginx
-----------------
@@ -97,7 +151,7 @@ A basic flask FastCGI configuration for nginx looks like this::
location /yourapplication { try_files $uri @yourapplication; }
location @yourapplication {
include fastcgi_params;
- fastcgi_split_path_info ^(/yourapplication)(.*)$;
+ fastcgi_split_path_info ^(/yourapplication)(.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_pass unix:/tmp/yourapplication-fcgi.sock;
@@ -160,4 +214,4 @@ python path. Common problems are:
.. _nginx: http://nginx.org/
.. _lighttpd: http://www.lighttpd.net/
.. _cherokee: http://www.cherokee-project.com/
-.. _flup: http://trac.saddi.com/flup
+.. _flup: http://trac.saddi.com/flup
\ No newline at end of file
From fb011878857693659c6ec6f095fe3849803915e1 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Fri, 20 Apr 2012 09:20:20 -0400
Subject: [PATCH 104/142] Touch up fastcgi doc.
---
docs/deploying/fastcgi.rst | 30 ++++++++++++++++--------------
1 file changed, 16 insertions(+), 14 deletions(-)
diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst
index 91824af08b..b280156072 100644
--- a/docs/deploying/fastcgi.rst
+++ b/docs/deploying/fastcgi.rst
@@ -54,16 +54,19 @@ can execute it:
Configuring Apache
------------------
-The example above is good enough for a basic Apache deployment but your `.fcgi` file will appear in your application URL e.g. www.example.com/yourapplication.fcgi/news/. There are few ways to resolve it. A preferable way is to use Apache ScriptAlias configuration directive::
+The example above is good enough for a basic Apache deployment but your `.fcgi`
+file will appear in your application URL
+e.g. example.com/yourapplication.fcgi/news/. There are few ways to configure
+your application so that yourapplication.fcgi does not appear in the URL. A
+preferable way is to use the ScriptAlias configuration directive::
ServerName example.com
ScriptAlias / /path/to/yourapplication.fcgi/
-Another way is to use a custom WSGI middleware. For example on a shared web hosting::
-
- .htaccess
+If you cannot set ScriptAlias, for example on an shared web host, you can use
+WSGI middleware to remove yourapplication.fcgi from the URLs. Set .htaccess::
AddHandler fcgid-script .fcgi
@@ -81,7 +84,7 @@ Another way is to use a custom WSGI middleware. For example on a shared web host
RewriteRule ^(.*)$ yourapplication.fcgi/$1 [QSA,L]
- yourapplication.fcgi
+Set yourapplication.fcgi::
#!/usr/bin/python
#: optional path to your local python site-packages folder
@@ -128,16 +131,15 @@ A basic FastCGI configuration for lighttpd looks like that::
"^(/static.*)$" => "$1",
"^(/.*)$" => "/yourapplication.fcgi$1"
-Remember to enable the FastCGI, alias and rewrite modules. This
-configuration binds the application to `/yourapplication`. If you want
-the application to work in the URL root you have to work around a
-lighttpd bug with the
+Remember to enable the FastCGI, alias and rewrite modules. This configuration
+binds the application to `/yourapplication`. If you want the application to
+work in the URL root you have to work around a lighttpd bug with the
:class:`~werkzeug.contrib.fixers.LighttpdCGIRootFix` middleware.
Make sure to apply it only if you are mounting the application the URL
-root. Also, see the Lighty docs for more information on `FastCGI and
-Python `_
-(note that explicitly passing a socket to run() is no longer necessary).
+root. Also, see the Lighty docs for more information on `FastCGI and Python
+`_ (note that
+explicitly passing a socket to run() is no longer necessary).
Configuring nginx
-----------------
@@ -151,7 +153,7 @@ A basic flask FastCGI configuration for nginx looks like this::
location /yourapplication { try_files $uri @yourapplication; }
location @yourapplication {
include fastcgi_params;
- fastcgi_split_path_info ^(/yourapplication)(.*)$;
+ fastcgi_split_path_info ^(/yourapplication)(.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_pass unix:/tmp/yourapplication-fcgi.sock;
@@ -214,4 +216,4 @@ python path. Common problems are:
.. _nginx: http://nginx.org/
.. _lighttpd: http://www.lighttpd.net/
.. _cherokee: http://www.cherokee-project.com/
-.. _flup: http://trac.saddi.com/flup
\ No newline at end of file
+.. _flup: http://trac.saddi.com/flup
From 0333c824bfa93fafabfb10b80f84d85abecda6bc Mon Sep 17 00:00:00 2001
From: Alex Vykalyuk
Date: Sat, 21 Apr 2012 22:27:24 +0300
Subject: [PATCH 105/142] Removed link to ep.io from quickstart
---
docs/quickstart.rst | 1 -
1 file changed, 1 deletion(-)
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 46290d0bc6..b9cbdf32bb 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -839,7 +839,6 @@ immediately deploy to a hosted platform, all of which offer a free plan for
small projects:
- `Deploying Flask on Heroku `_
-- `Deploying Flask on ep.io `_
- `Deploying WSGI on dotCloud `_
with `Flask-specific notes `_
From d90f0afe39724040d0be92df054e2b1438886134 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Sat, 21 Apr 2012 18:40:02 -0400
Subject: [PATCH 106/142] Add test for jsonify padded=False, #495.
---
flask/testsuite/helpers.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py
index be26864539..4781d2d926 100644
--- a/flask/testsuite/helpers.py
+++ b/flask/testsuite/helpers.py
@@ -73,14 +73,17 @@ def return_kwargs():
@app.route('/dict')
def return_dict():
return flask.jsonify(d)
+ @app.route("/unpadded")
+ def return_padded_false():
+ return flask.jsonify(d, padded=False)
@app.route("/padded")
- def return_padded_json():
+ def return_padded_true():
return flask.jsonify(d, padded=True)
@app.route("/padded_custom")
def return_padded_json_custom_callback():
return flask.jsonify(d, padded='my_func_name')
c = app.test_client()
- for url in '/kw', '/dict':
+ for url in '/kw', '/dict', '/unpadded':
rv = c.get(url)
self.assert_equal(rv.mimetype, 'application/json')
self.assert_equal(flask.json.loads(rv.data), d)
From 36194697ae8371f42d3041c1f89b6f2e77c34826 Mon Sep 17 00:00:00 2001
From: ekoka
Date: Sat, 21 Apr 2012 23:36:08 -0300
Subject: [PATCH 107/142] Update flask/app.py
---
flask/app.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flask/app.py b/flask/app.py
index 4c00c36b8c..4328c35b53 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -1468,7 +1468,7 @@ def inject_url_defaults(self, endpoint, values):
"""
funcs = self.url_default_functions.get(None, ())
if '.' in endpoint:
- bp = endpoint.split('.', 1)[0]
+ bp = endpoint.rsplit('.', 1)[0]
funcs = chain(funcs, self.url_default_functions.get(bp, ()))
for func in funcs:
func(endpoint, values)
From bb31188ec3882e9a6c6035b7a65ca257d424e31a Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Sun, 22 Apr 2012 12:30:15 -0400
Subject: [PATCH 108/142] Add a BuildError hook to url_for, #456.
---
flask/app.py | 20 ++++++++++++++++++++
flask/helpers.py | 14 ++++++++++++++
flask/testsuite/basic.py | 12 ++++++++++++
3 files changed, 46 insertions(+)
diff --git a/flask/app.py b/flask/app.py
index 4c00c36b8c..c0a9dac3ad 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -329,6 +329,17 @@ def __init__(self, import_name, static_path=None, static_url_path=None,
#: decorator.
self.error_handler_spec = {None: self._error_handlers}
+ #: If not `None`, this function is called when :meth:`url_for` raises
+ #: :exc:`~werkzeug.routing.BuildError`, with the call signature::
+ #:
+ #: self.build_error_handler(error, endpoint, **values)
+ #:
+ #: Here, `error` is the instance of `BuildError`, and `endpoint` and
+ #: `**values` are the arguments passed into :meth:`url_for`.
+ #:
+ #: .. versionadded:: 0.9
+ self.build_error_handler = None
+
#: A dictionary with lists of functions that should be called at the
#: beginning of the request. The key of the dictionary is the name of
#: the blueprint this function is active for, `None` for all requests.
@@ -1473,6 +1484,15 @@ def inject_url_defaults(self, endpoint, values):
for func in funcs:
func(endpoint, values)
+ def handle_build_error(self, error, endpoint, **values):
+ """Handle :class:`~werkzeug.routing.BuildError` on :meth:`url_for`.
+
+ Calls :attr:`build_error_handler` if it is not `None`.
+ """
+ if self.build_error_handler is None:
+ raise error
+ return self.build_error_handler(error, endpoint, **values)
+
def preprocess_request(self):
"""Called before the actual request dispatching and will
call every as :meth:`before_request` decorated function.
diff --git a/flask/helpers.py b/flask/helpers.py
index 238d7df75c..21b010b48e 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -20,6 +20,7 @@
from time import time
from zlib import adler32
from threading import RLock
+from werkzeug.routing import BuildError
from werkzeug.urls import url_quote
# try to load the best simplejson implementation available. If JSON
@@ -214,6 +215,10 @@ def url_for(endpoint, **values):
.. versionadded:: 0.9
The `_anchor` and `_method` parameters were added.
+ .. versionadded:: 0.9
+ Calls :meth:`Flask.handle_build_error` on
+ :exc:`~werkzeug.routing.BuildError`.
+
:param endpoint: the endpoint of the URL (name of the function)
:param values: the variable arguments of the URL rule
:param _external: if set to `True`, an absolute URL is generated.
@@ -260,6 +265,15 @@ def url_for(endpoint, **values):
anchor = values.pop('_anchor', None)
method = values.pop('_method', None)
appctx.app.inject_url_defaults(endpoint, values)
+ try:
+ rv = url_adapter.build(endpoint, values, method=method,
+ force_external=external)
+ except BuildError, error:
+ values['_external'] = external
+ values['_anchor'] = anchor
+ values['_method'] = method
+ return appctx.app.handle_build_error(error, endpoint, **values)
+
rv = url_adapter.build(endpoint, values, method=method,
force_external=external)
if anchor is not None:
diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py
index 0a4b1d9c84..d138c45e20 100644
--- a/flask/testsuite/basic.py
+++ b/flask/testsuite/basic.py
@@ -19,6 +19,7 @@
from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning
from werkzeug.exceptions import BadRequest, NotFound
from werkzeug.http import parse_date
+from werkzeug.routing import BuildError
class BasicFunctionalityTestCase(FlaskTestCase):
@@ -695,6 +696,17 @@ def hello():
self.assert_equal(flask.url_for('hello', name='test x', _external=True),
'http://localhost/hello/test%20x')
+ def test_build_error_handler(self):
+ app = flask.Flask(__name__)
+ with app.test_request_context():
+ self.assertRaises(BuildError, flask.url_for, 'spam')
+ def handler(error, endpoint, **values):
+ # Just a test.
+ return '/test_handler/'
+ app.build_error_handler = handler
+ with app.test_request_context():
+ self.assert_equal(flask.url_for('spam'), '/test_handler/')
+
def test_custom_converters(self):
from werkzeug.routing import BaseConverter
class ListConverter(BaseConverter):
From 8c8c524ddb791c2f7eed47b6d2e4317faf06a659 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Sun, 22 Apr 2012 12:51:31 -0400
Subject: [PATCH 109/142] Re-raise BuildError with traceback.
---
flask/app.py | 6 +++++-
flask/testsuite/basic.py | 15 +++++++++++++++
2 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/flask/app.py b/flask/app.py
index c0a9dac3ad..97dc5bde48 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -1490,7 +1490,11 @@ def handle_build_error(self, error, endpoint, **values):
Calls :attr:`build_error_handler` if it is not `None`.
"""
if self.build_error_handler is None:
- raise error
+ exc_type, exc_value, tb = sys.exc_info()
+ if exc_value is error:
+ raise exc_type, exc_value, tb
+ else:
+ raise error
return self.build_error_handler(error, endpoint, **values)
def preprocess_request(self):
diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py
index d138c45e20..cf7590cbfe 100644
--- a/flask/testsuite/basic.py
+++ b/flask/testsuite/basic.py
@@ -698,8 +698,23 @@ def hello():
def test_build_error_handler(self):
app = flask.Flask(__name__)
+
+ # Test base case, a URL which results in a BuildError.
with app.test_request_context():
self.assertRaises(BuildError, flask.url_for, 'spam')
+
+ # Verify the error is re-raised if not the current exception.
+ try:
+ with app.test_request_context():
+ flask.url_for('spam')
+ except BuildError, error:
+ pass
+ try:
+ raise RuntimeError('Test case where BuildError is not current.')
+ except RuntimeError:
+ self.assertRaises(BuildError, app.handle_build_error, error, 'spam')
+
+ # Test a custom handler.
def handler(error, endpoint, **values):
# Just a test.
return '/test_handler/'
From 028229d0ff531db172f8cda52210de8903dbc14b Mon Sep 17 00:00:00 2001
From: Alex Vykalyuk
Date: Mon, 23 Apr 2012 00:32:48 +0300
Subject: [PATCH 110/142] Fixed typo in docs/quickstart
---
docs/quickstart.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index b9cbdf32bb..e8b71ca98a 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -526,7 +526,7 @@ deal with that problem.
To access parameters submitted in the URL (``?key=value``) you can use the
:attr:`~flask.request.args` attribute::
- searchword = request.args.get('q', '')
+ searchword = request.args.get('key', '')
We recommend accessing URL parameters with `get` or by catching the
`KeyError` because users might change the URL and presenting them a 400
From 148c50abf983eb8bbd8ecb565c9f98912048af46 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Mon, 23 Apr 2012 21:20:47 -0400
Subject: [PATCH 111/142] Document url_for BuildError hook.
---
flask/app.py | 1 +
flask/helpers.py | 34 ++++++++++++++++++++++++++++++++++
2 files changed, 35 insertions(+)
diff --git a/flask/app.py b/flask/app.py
index 97dc5bde48..67383bbe15 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -1492,6 +1492,7 @@ def handle_build_error(self, error, endpoint, **values):
if self.build_error_handler is None:
exc_type, exc_value, tb = sys.exc_info()
if exc_value is error:
+ # exception is current, raise in context of original traceback.
raise exc_type, exc_value, tb
else:
raise error
diff --git a/flask/helpers.py b/flask/helpers.py
index 21b010b48e..5560d38c9d 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -212,6 +212,40 @@ def url_for(endpoint, **values):
For more information, head over to the :ref:`Quickstart `.
+ To integrate applications, :class:`Flask` has a hook to intercept URL build
+ errors through :attr:`Flask.build_error_handler`. The `url_for` function
+ results in a :exc:`~werkzeug.routing.BuildError` when the current app does
+ not have a URL for the given endpoint and values. When it does, the
+ :data:`~flask.current_app` calls its :attr:`~Flask.build_error_handler` if
+ it is not `None`, which can return a string to use as the result of
+ `url_for` (instead of `url_for`'s default to raise the
+ :exc:`~werkzeug.routing.BuildError` exception) or re-raise the exception.
+ An example::
+
+ def external_url_handler(error, endpoint, **values):
+ "Looks up an external URL when `url_for` cannot build a URL."
+ # This is an example of hooking the build_error_handler.
+ # Here, lookup_url is some utility function you've built
+ # which looks up the endpoint in some external URL registry.
+ url = lookup_url(endpoint, **values)
+ if url is None:
+ # External lookup did not have a URL.
+ # Re-raise the BuildError, in context of original traceback.
+ exc_type, exc_value, tb = sys.exc_info()
+ if exc_value is error:
+ raise exc_type, exc_value, tb
+ else:
+ raise error
+ # url_for will use this result, instead of raising BuildError.
+ return url
+
+ app.build_error_handler = external_url_handler
+
+ Here, `error` is the instance of :exc:`~werkzeug.routing.BuildError`, and
+ `endpoint` and `**values` are the arguments passed into `url_for`. Note
+ that this is for building URLs outside the current application, and not for
+ handling 404 NotFound errors.
+
.. versionadded:: 0.9
The `_anchor` and `_method` parameters were added.
From 2262ce4915aec0dfffa8e71244ebe10f72d11111 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Mon, 23 Apr 2012 21:36:28 -0400
Subject: [PATCH 112/142] Skip template leak test when not CPython2.7, #452.
---
flask/testsuite/regression.py | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/flask/testsuite/regression.py b/flask/testsuite/regression.py
index 51a866a4af..bc37afc407 100644
--- a/flask/testsuite/regression.py
+++ b/flask/testsuite/regression.py
@@ -72,9 +72,12 @@ def fire():
# Trigger caches
fire()
- with self.assert_no_leak():
- for x in xrange(10):
- fire()
+ # This test only works on CPython 2.7.
+ if sys.version_info >= (2, 7) and \
+ not hasattr(sys, 'pypy_translation_info'):
+ with self.assert_no_leak():
+ for x in xrange(10):
+ fire()
def suite():
From b31f2d9a640c154e41de6d9631e95cf105e96e1f Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Mon, 23 Apr 2012 21:46:53 -0400
Subject: [PATCH 113/142] Require Werkzeug>=0.7, #449.
---
README | 9 +++++----
setup.py | 2 +-
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/README b/README
index 7d5ada23c8..317080a7b8 100644
--- a/README
+++ b/README
@@ -17,10 +17,11 @@
~ What do I need?
- Jinja 2.4 and Werkzeug 0.6.1. `pip` or `easy_install` will
- install them for you if you do `easy_install Flask`.
- I encourage you to use a virtualenv. Check the docs for
- complete installation and usage instructions.
+ Jinja 2.4 and Werkzeug 0.7 or later.
+ `pip` or `easy_install` will install them for you if you do
+ `pip install Flask`. I encourage you to use a virtualenv.
+ Check the docs for complete installation and usage
+ instructions.
~ Where are the docs?
diff --git a/setup.py b/setup.py
index 8169a51746..fdc4653e15 100644
--- a/setup.py
+++ b/setup.py
@@ -90,7 +90,7 @@ def run(self):
zip_safe=False,
platforms='any',
install_requires=[
- 'Werkzeug>=0.6.1',
+ 'Werkzeug>=0.7',
'Jinja2>=2.4'
],
classifiers=[
From ff5ee034b8c71a79d3f29c7b7a1ad27f6a8893e3 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Mon, 23 Apr 2012 21:47:28 -0400
Subject: [PATCH 114/142] Touch up README.
---
README | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/README b/README
index 317080a7b8..7297f5db1b 100644
--- a/README
+++ b/README
@@ -6,7 +6,7 @@
~ What is Flask?
Flask is a microframework for Python based on Werkzeug
- and Jinja2. It's intended for small scale applications
+ and Jinja2. It's intended for getting started very quickly
and was developed with best intentions in mind.
~ Is it ready?
@@ -51,3 +51,5 @@
Either use the #pocoo IRC channel on irc.freenode.net or
ask on the mailinglist: http://flask.pocoo.org/mailinglist/
+
+ See http://flask.pocoo.org/community/ for more resources.
From 7c79ce6e418f07a49be8c25a4c5a40e5347be257 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Mon, 23 Apr 2012 23:42:58 -0400
Subject: [PATCH 115/142] Revise foreword and Becoming Big docs, #484.
---
docs/advanced_foreword.rst | 65 ++++++++++++------------
docs/becomingbig.rst | 100 +++++++++++++++++++++----------------
docs/contents.rst.inc | 1 +
docs/foreword.rst | 76 ++++++++++++++--------------
4 files changed, 129 insertions(+), 113 deletions(-)
diff --git a/docs/advanced_foreword.rst b/docs/advanced_foreword.rst
index cc1a1843e8..1831dd594c 100644
--- a/docs/advanced_foreword.rst
+++ b/docs/advanced_foreword.rst
@@ -1,27 +1,26 @@
+.. _advanced_foreword:
+
Foreword for Experienced Programmers
====================================
-This chapter is for programmers who have worked with other frameworks in the
-past, and who may have more specific or esoteric concerns that the typical
-user.
-
-Threads in Flask
-----------------
+Thread-Locals in Flask
+----------------------
-One of the design decisions with Flask was that simple tasks should be simple;
+One of the design decisions in Flask was that simple tasks should be simple;
they should not take a lot of code and yet they should not limit you. Because
-of that we made a few design choices that some people might find surprising or
-unorthodox. For example, Flask uses thread-local objects internally so that
-you don’t have to pass objects around from function to function within a
-request in order to stay threadsafe. While this is a really easy approach and
-saves you a lot of time, it might also cause some troubles for very large
-applications because changes on these thread-local objects can happen anywhere
-in the same thread. In order to solve these problems we don’t hide the thread
-locals for you but instead embrace them and provide you with a lot of tools to
-make it as pleasant as possible to work with them.
+of that, Flask has few design choices that some people might find surprising or
+unorthodox. For example, Flask uses thread-local objects internally so that you
+don’t have to pass objects around from function to function within a request in
+order to stay threadsafe. This approach is convenient, but requires a valid
+request context for dependency injection or when attempting to reuse code which
+uses a value pegged to the request. The Flask project is honest about
+thread-locals, does not hide them, and calls out in the code and documentation
+where they are used.
-Web Development is Dangerous
-----------------------------
+Develop for the Web with Caution
+--------------------------------
+
+Always keep security in mind when building web applications.
If you write a web application, you are probably allowing users to register
and leave their data on your server. The users are entrusting you with data.
@@ -30,22 +29,22 @@ you still want that data to be stored securely.
Unfortunately, there are many ways the security of a web application can be
compromised. Flask protects you against one of the most common security
-problems of modern web applications: cross-site scripting (XSS). Unless
-you deliberately mark insecure HTML as secure, Flask and the underlying
-Jinja2 template engine have you covered. But there are many more ways to
-cause security problems.
+problems of modern web applications: cross-site scripting (XSS). Unless you
+deliberately mark insecure HTML as secure, Flask and the underlying Jinja2
+template engine have you covered. But there are many more ways to cause
+security problems.
-The documentation will warn you about aspects of web development that
-require attention to security. Some of these security concerns
-are far more complex than one might think, and we all sometimes underestimate
-the likelihood that a vulnerability will be exploited - until a clever
-attacker figures out a way to exploit our applications. And don't think
-that your application is not important enough to attract an attacker.
-Depending on the kind of attack, chances are that automated bots are
-probing for ways to fill your database with spam, links to malicious
-software, and the like.
+The documentation will warn you about aspects of web development that require
+attention to security. Some of these security concerns are far more complex
+than one might think, and we all sometimes underestimate the likelihood that a
+vulnerability will be exploited - until a clever attacker figures out a way to
+exploit our applications. And don't think that your application is not
+important enough to attract an attacker. Depending on the kind of attack,
+chances are that automated bots are probing for ways to fill your database with
+spam, links to malicious software, and the like.
-So always keep security in mind when doing web development.
+Flask is no different from any other framework in that you the developer must
+build with caution, watching for exploits when building to your requirements.
The Status of Python 3
----------------------
@@ -65,3 +64,5 @@ using Python 2.6 and 2.7 with activated Python 3 warnings during
development. If you plan on upgrading to Python 3 in the near future we
strongly recommend that you read `How to write forwards compatible
Python code `_.
+
+Continue to :ref:`installation` or the :ref:`quickstart`.
diff --git a/docs/becomingbig.rst b/docs/becomingbig.rst
index 20a0186e2e..ca8030603b 100644
--- a/docs/becomingbig.rst
+++ b/docs/becomingbig.rst
@@ -3,45 +3,57 @@
Becoming Big
============
-Your application is becoming more and more complex? If you suddenly
-realize that Flask does things in a way that does not work out for your
-application there are ways to deal with that.
-
-Flask is powered by Werkzeug and Jinja2, two libraries that are in use at
-a number of large websites out there and all Flask does is bring those
-two together. Being a microframework Flask does not do much more than
-combining existing libraries - there is not a lot of code involved.
-What that means for large applications is that it's very easy to take the
-code from Flask and put it into a new module within the applications and
-expand on that.
-
-Flask is designed to be extended and modified in a couple of different
-ways:
-
-- Flask extensions. For a lot of reusable functionality you can create
- extensions. For extensions a number of hooks exist throughout Flask
- with signals and callback functions.
-
-- Subclassing. The majority of functionality can be changed by creating
- a new subclass of the :class:`~flask.Flask` class and overriding
- methods provided for this exact purpose.
-
-- Forking. If nothing else works out you can just take the Flask
- codebase at a given point and copy/paste it into your application
- and change it. Flask is designed with that in mind and makes this
- incredible easy. You just have to take the package and copy it
- into your application's code and rename it (for example to
- `framework`). Then you can start modifying the code in there.
-
-Why consider Forking?
+Here are your options when growing your codebase or scaling your application.
+
+Read the Source.
+----------------
+
+Flask started in part to demonstrate how to build your own framework on top of
+existing well-used tools Werkzeug (WSGI) and Jinja (templating), and as it
+developed, it became useful to a wide audience. As you grow your codebase,
+don't just use Flask -- understand it. Read the source. Flask's code is
+written to be read; it's documentation published so you can use its internal
+APIs. Flask sticks to documented APIs in upstream libraries, and documents its
+internal utilities so that you can find the hook points needed for your
+project.
+
+Hook. Extend.
+-------------
+
+The :ref:`api` docs are full of available overrides, hook points, and
+:ref:`signals`. You can provide custom classes for things like the request and
+response objects. Dig deeper on the APIs you use, and look for the
+customizations which are available out of the box in a Flask release. Look for
+ways in which your project can be refactored into a collection of utilities and
+Flask extensions. Explore the many extensions in the community, and look for
+patterns to build your own extensions if you do not find the tools you need.
+
+Subclass.
+---------
+
+The :class:`~flask.Flask` class has many methods designed for subclassing. You
+can quickly add or customize behavior by subclassing :class:`~flask.Flask` (see
+the linked method docs) and using that subclass wherever you instantiate an
+application class. This works well with :ref:`app-factories`.
+
+Wrap with middleware.
---------------------
-The majority of code of Flask is within Werkzeug and Jinja2. These
-libraries do the majority of the work. Flask is just the paste that glues
-those together. For every project there is the point where the underlying
-framework gets in the way (due to assumptions the original developers
-had). This is natural because if this would not be the case, the
-framework would be a very complex system to begin with which causes a
+The :ref:`app-dispatch` chapter shows in detail how to apply middleware. You
+can introduce WSGI middleware to wrap your Flask instances and introduce fixes
+and changes at the layer between your Flask application and your HTTP
+server. Werkzeug includes several `middlewares
+`_.
+
+Fork.
+-----
+
+If none of the above options work, fork Flask. The majority of code of Flask
+is within Werkzeug and Jinja2. These libraries do the majority of the work.
+Flask is just the paste that glues those together. For every project there is
+the point where the underlying framework gets in the way (due to assumptions
+the original developers had). This is natural because if this would not be the
+case, the framework would be a very complex system to begin with which causes a
steep learning curve and a lot of user frustration.
This is not unique to Flask. Many people use patched and modified
@@ -55,8 +67,8 @@ Furthermore integrating upstream changes can be a complex process,
depending on the number of changes. Because of that, forking should be
the very last resort.
-Scaling like a Pro
-------------------
+Scale like a pro.
+-----------------
For many web applications the complexity of the code is less an issue than
the scaling for the number of users or data entries expected. Flask by
@@ -78,11 +90,11 @@ majority of servers are using either threads, greenlets or separate
processes to achieve concurrency which are all methods well supported by
the underlying Werkzeug library.
-Dialogue with the Community
+Discuss with the community.
---------------------------
-The Flask developers are very interested to keep everybody happy, so as
-soon as you find an obstacle in your way, caused by Flask, don't hesitate
-to contact the developers on the mailinglist or IRC channel. The best way
-for the Flask and Flask-extension developers to improve it for larger
+The Flask developers keep the framework accessible to users with codebases big
+and small. If you find an obstacle in your way, caused by Flask, don't hesitate
+to contact the developers on the mailinglist or IRC channel. The best way for
+the Flask and Flask extension developers to improve the tools for larger
applications is getting feedback from users.
diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc
index a1893c4886..b60c7a0390 100644
--- a/docs/contents.rst.inc
+++ b/docs/contents.rst.inc
@@ -9,6 +9,7 @@ instructions for web development with Flask.
:maxdepth: 2
foreword
+ advanced_foreword
installation
quickstart
tutorial/index
diff --git a/docs/foreword.rst b/docs/foreword.rst
index b186aba655..167f2f413c 100644
--- a/docs/foreword.rst
+++ b/docs/foreword.rst
@@ -8,48 +8,50 @@ should or should not be using it.
What does "micro" mean?
-----------------------
-“Micro” does not mean that your whole web application has to fit into
-a single Python file (although it certainly can). Nor does it mean
-that Flask is lacking in functionality. The "micro" in microframework
-means Flask aims to keep the core simple but extensible. Flask won't make
-many decisions for you, such as what database to use. Those decisions that
-it does make, such as what templating engine to use, are easy to change.
-Everything else is up to you, so that Flask can be everything you need
-and nothing you don't.
+“Micro” does not mean that your whole web application has to fit into a single
+Python file, although it certainly can. Nor does it mean that Flask is lacking
+in functionality. The "micro" in microframework means Flask aims to keep the
+core simple but extensible. Flask won't make many decisions for you, such as
+what database to use. Those decisions that it does make, such as what
+templating engine to use, are easy to change. Everything else is up to you, so
+that Flask can be everything you need and nothing you don't.
By default, Flask does not include a database abstraction layer, form
validation or anything else where different libraries already exist that can
-handle that. Instead, FLask extensions add such functionality to your
-application as if it was implemented in Flask itself. Numerous extensions
+handle that. Instead, Flask supports extensions to add such functionality to
+your application as if it was implemented in Flask itself. Numerous extensions
provide database integration, form validation, upload handling, various open
-authentication technologies, and more. Flask may be "micro", but the
-possibilities are endless.
+authentication technologies, and more. Flask may be "micro", but it's ready for
+production use on a variety of needs.
-Convention over Configuration
+Configuration and Conventions
-----------------------------
-Flask is based on convention over configuration, which means that many things
-are preconfigured. For example, by convention templates and static files are
-stored in subdirectories within the application's Python source tree. While
-this can be changed you usually don't have to. We want to minimize the time
-you need to spend in order to get up and running, without assuming things
-about your needs.
-
-Growing Up
-----------
-
-Since Flask is based on a very solid foundation there is not a lot of code in
-Flask itself. As such it's easy to adapt even for large applications and we
-are making sure that you can either configure it as much as possible by
-subclassing things or by forking the entire codebase. If you are interested
-in that, check out the :ref:`becomingbig` chapter.
-
-If you are curious about the Flask design principles, head over to the section
-about :ref:`design`.
-
-For the Stalwart and Wizened...
--------------------------------
-
-If you're more curious about the minutiae of Flask's implementation, and
-whether its structure is right for your needs, read the
+Flask has many configuration values, with sensible defaults, and a few
+conventions when getting started. By convention templates and static files are
+stored in subdirectories within the application's Python source tree, with the
+names `templates` and `static` respectively. While this can be changed you
+usually don't have to, especially when getting started.
+
+Growing with Flask
+------------------
+
+Once you have Flask up and running, you'll find a variety of extensions
+available in the community to integrate your project for production. The Flask
+core team reviews extensions and ensures approved extensions do not break with
+future releases.
+
+As your codebase grows, you are free to make the design decisions appropriate
+for your project. Flask will continue to provide a very simple glue layer to
+the best that Python has to offer. You can implement advanced patterns in
+SQLAlchemy or another database tool, introduce non-relational data persistence
+as appropriate, and take advantage of framework-agnostic tools built for WSGI,
+the Python web interface.
+
+Flask includes many hooks to customize its behavior. Should you need more
+customization, the Flask class is built for subclassing. If you are interested
+in that, check out the :ref:`becomingbig` chapter. If you are curious about
+the Flask design principles, head over to the section about :ref:`design`.
+
+Continue to :ref:`installation`, the :ref:`quickstart`, or the
:ref:`advanced_foreword`.
From 26da6a5365e1fd229932f167a299128f30fae154 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Tue, 24 Apr 2012 01:48:05 -0400
Subject: [PATCH 116/142] Use default send_file max-age consistently.
Prior to this commit, the send_file max-age hook and config were only
used for the static file handler. Now they are used when calling
helpers.send_file directly.
---
CHANGES | 17 ++++++------
docs/config.rst | 11 +++++---
flask/app.py | 6 -----
flask/helpers.py | 51 ++++++++++++++++++++++-------------
flask/testsuite/blueprints.py | 23 ++++++++++++++++
flask/testsuite/helpers.py | 25 +++++++++++------
6 files changed, 88 insertions(+), 45 deletions(-)
diff --git a/CHANGES b/CHANGES
index 83c1f4fe4b..537f654493 100644
--- a/CHANGES
+++ b/CHANGES
@@ -53,14 +53,15 @@ 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_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.
+- :class:`~flask.Flask` and :class:`~flask.Blueprint` now provide a
+ :meth:`~flask.Flask.get_send_file_max_age` hook for subclasses to override
+ behavior of serving static files from Flask when using
+ :meth:`flask.Flask.send_static_file` (used for the default static file
+ handler) and :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_file` and static files can be configured
+ through a new ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable, which is
+ used in the default `get_send_file_max_age` implementation.
- Fixed an assumption in sessions implementation which could break message
flashing on sessions implementations which use external storage.
- Changed the behavior of tuple return values from functions. They are no
diff --git a/docs/config.rst b/docs/config.rst
index 86bfb0d1a6..7a32fb84ee 100644
--- a/docs/config.rst
+++ b/docs/config.rst
@@ -111,12 +111,15 @@ The following configuration values are used internally by Flask:
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
+ :meth:`~flask.Flask.send_static_file` (the
+ default static file handler) and
+ :func:`~flask.send_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).
+ :meth:`~flask.Flask.get_send_file_max_age`
+ hook on :class:`~flask.Flask` or
+ :class:`~flask.Blueprint`,
+ respectively. 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
diff --git a/flask/app.py b/flask/app.py
index 67383bbe15..5f809abb2e 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -1041,12 +1041,6 @@ 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.
diff --git a/flask/helpers.py b/flask/helpers.py
index 5560d38c9d..05f84ef72b 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -406,7 +406,7 @@ def get_flashed_messages(with_categories=False, category_filter=[]):
def send_file(filename_or_fp, mimetype=None, as_attachment=False,
attachment_filename=None, add_etags=True,
- cache_timeout=60 * 60 * 12, conditional=False):
+ cache_timeout=None, conditional=False):
"""Sends the contents of a file to the client. This will use the
most efficient method available and configured. By default it will
try to use the WSGI server's file_wrapper support. Alternatively
@@ -420,10 +420,6 @@ 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::
@@ -443,6 +439,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
able to, otherwise attach an etag yourself. This functionality
will be removed in Flask 1.0
+ .. versionchanged:: 0.9
+ cache_timeout pulls its default from application config, when None.
+
:param filename_or_fp: the filename of the file to send. This is
relative to the :attr:`~Flask.root_path` if a
relative path is specified.
@@ -459,7 +458,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
differs from the file's filename.
:param add_etags: set to `False` to disable attaching of etags.
:param conditional: set to `True` to enable conditional responses.
- :param cache_timeout: the timeout in seconds for the headers.
+
+ :param cache_timeout: the timeout in seconds for the headers. When `None`
+ (default), this value is set by
+ :meth:`~Flask.get_send_file_max_age` of
+ :data:`~flask.current_app`.
"""
mtime = None
if isinstance(filename_or_fp, basestring):
@@ -523,6 +526,8 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
rv.last_modified = int(mtime)
rv.cache_control.public = True
+ if cache_timeout is None:
+ cache_timeout = current_app.get_send_file_max_age(filename)
if cache_timeout:
rv.cache_control.max_age = cache_timeout
rv.expires = int(time() + cache_timeout)
@@ -757,26 +762,31 @@ def jinja_loader(self):
return FileSystemLoader(os.path.join(self.root_path,
self.template_folder))
- def get_send_file_options(self, filename):
- """Provides keyword arguments to send to :func:`send_from_directory`.
+ def get_send_file_max_age(self, filename):
+ """Provides default cache_timeout for the :func:`send_file` functions.
+
+ By default, this function returns ``SEND_FILE_MAX_AGE_DEFAULT`` from
+ the configuration of :data:`~flask.current_app`.
+
+ Static file functions such as :func:`send_from_directory` use this
+ function, and :func:`send_file` calls this function on
+ :data:`~flask.current_app` when the given cache_timeout is `None`. If a
+ cache_timeout is given in :func:`send_file`, that timeout is used;
+ otherwise, this method is called.
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`)::
+ to 60 seconds::
class MyFlask(flask.Flask):
- 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
+ def get_send_file_max_age(self, name):
+ if name.lower().endswith('.js'):
+ return 60
+ return flask.Flask.get_send_file_max_age(self, name)
.. versionadded:: 0.9
"""
- options = {}
- options['cache_timeout'] = current_app.config['SEND_FILE_MAX_AGE_DEFAULT']
- return options
+ return current_app.config['SEND_FILE_MAX_AGE_DEFAULT']
def send_static_file(self, filename):
"""Function used internally to send static files from the static
@@ -786,8 +796,11 @@ def send_static_file(self, filename):
"""
if not self.has_static_folder:
raise RuntimeError('No static folder for this object')
+ # Ensure get_send_file_max_age is called in all cases.
+ # Here, we ensure get_send_file_max_age is called for Blueprints.
+ cache_timeout = self.get_send_file_max_age(filename)
return send_from_directory(self.static_folder, filename,
- **self.get_send_file_options(filename))
+ cache_timeout=cache_timeout)
def open_resource(self, resource, mode='rb'):
"""Opens a resource from the application's resource folder. To see
diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py
index 5f3d3ab35a..c962212199 100644
--- a/flask/testsuite/blueprints.py
+++ b/flask/testsuite/blueprints.py
@@ -386,6 +386,29 @@ def test_templates_and_static(self):
with flask.Flask(__name__).test_request_context():
self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested')
+ def test_default_static_cache_timeout(self):
+ app = flask.Flask(__name__)
+ class MyBlueprint(flask.Blueprint):
+ def get_send_file_max_age(self, filename):
+ return 100
+
+ blueprint = MyBlueprint('blueprint', __name__, static_folder='static')
+ app.register_blueprint(blueprint)
+
+ # try/finally, in case other tests use this app for Blueprint tests.
+ max_age_default = app.config['SEND_FILE_MAX_AGE_DEFAULT']
+ try:
+ with app.test_request_context():
+ unexpected_max_age = 3600
+ if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == unexpected_max_age:
+ unexpected_max_age = 7200
+ app.config['SEND_FILE_MAX_AGE_DEFAULT'] = unexpected_max_age
+ rv = blueprint.send_static_file('index.html')
+ cc = parse_cache_control_header(rv.headers['Cache-Control'])
+ self.assert_equal(cc.max_age, 100)
+ finally:
+ app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default
+
def test_templates_list(self):
from blueprintapp import app
templates = sorted(app.jinja_env.list_templates())
diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py
index 4781d2d926..a0e60aac77 100644
--- a/flask/testsuite/helpers.py
+++ b/flask/testsuite/helpers.py
@@ -237,28 +237,37 @@ def test_static_file(self):
app = flask.Flask(__name__)
# default cache timeout is 12 hours
with app.test_request_context():
+ # Test with static file handler.
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)
+ # Test again with direct use of send_file utility.
+ rv = flask.send_file('static/index.html')
+ cc = parse_cache_control_header(rv.headers['Cache-Control'])
+ self.assert_equal(cc.max_age, 12 * 60 * 60)
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600
with app.test_request_context():
+ # Test with static file handler.
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
+ # Test again with direct use of send_file utility.
+ rv = flask.send_file('static/index.html')
+ cc = parse_cache_control_header(rv.headers['Cache-Control'])
+ self.assert_equal(cc.max_age, 3600)
class StaticFileApp(flask.Flask):
- 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
- opts['conditional'] = True
- return opts
+ def get_send_file_max_age(self, filename):
+ return 10
app = StaticFileApp(__name__)
with app.test_request_context():
+ # Test with static file handler.
rv = app.send_static_file('index.html')
cc = parse_cache_control_header(rv.headers['Cache-Control'])
self.assert_equal(cc.max_age, 10)
+ # Test again with direct use of send_file utility.
+ rv = flask.send_file('static/index.html')
+ cc = parse_cache_control_header(rv.headers['Cache-Control'])
+ self.assert_equal(cc.max_age, 10)
class LoggingTestCase(FlaskTestCase):
From 33bae1a8dcadbf56fb19ad814fa516b6468bb2cb Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Wed, 18 Apr 2012 20:46:07 -0400
Subject: [PATCH 117/142] Add Flask.request_globals_class to customize g.
Requested by toothr on #pocoo.
---
CHANGES | 2 ++
flask/app.py | 7 ++++++-
flask/ctx.py | 3 ++-
flask/testsuite/appctx.py | 10 ++++++++++
4 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/CHANGES b/CHANGES
index 537f654493..10cd1c08a4 100644
--- a/CHANGES
+++ b/CHANGES
@@ -66,6 +66,8 @@ Relase date to be decided, codename to be chosen.
flashing on sessions implementations which use external storage.
- Changed the behavior of tuple return values from functions. They are no
longer arguments to the response object, they now have a defined meaning.
+- Added :attr:`flask.Flask.request_globals_class` to allow a specific class to
+ be used on creation of the :data:`~flask.g` instance of each request.
Version 0.8.1
-------------
diff --git a/flask/app.py b/flask/app.py
index 5f809abb2e..1a2961e177 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -28,7 +28,7 @@
find_package
from .wrappers import Request, Response
from .config import ConfigAttribute, Config
-from .ctx import RequestContext, AppContext
+from .ctx import RequestContext, AppContext, _RequestGlobals
from .globals import _request_ctx_stack, request
from .sessions import SecureCookieSessionInterface
from .module import blueprint_is_module
@@ -148,6 +148,11 @@ class Flask(_PackageBoundObject):
#: :class:`~flask.Response` for more information.
response_class = Response
+ #: The class that is used for the :data:`~flask.g` instance.
+ #:
+ #: .. versionadded:: 0.9
+ request_globals_class = _RequestGlobals
+
#: The debug flag. Set this to `True` to enable debugging of the
#: application. In debug mode the debugger will kick in when an unhandled
#: exception ocurrs and the integrated server will automatically reload
diff --git a/flask/ctx.py b/flask/ctx.py
index 16b0350310..cf197d05dd 100644
--- a/flask/ctx.py
+++ b/flask/ctx.py
@@ -18,6 +18,7 @@
class _RequestGlobals(object):
+ """A plain object."""
pass
@@ -139,7 +140,7 @@ def __init__(self, app, environ):
self.app = app
self.request = app.request_class(environ)
self.url_adapter = app.create_url_adapter(self.request)
- self.g = _RequestGlobals()
+ self.g = app.request_globals_class()
self.flashes = None
self.session = None
diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py
index a4ad479b6f..1dcdb406f1 100644
--- a/flask/testsuite/appctx.py
+++ b/flask/testsuite/appctx.py
@@ -65,6 +65,16 @@ def cleanup(exception):
self.assert_equal(cleanup_stuff, [None])
+ def test_custom_request_globals_class(self):
+ class CustomRequestGlobals(object):
+ def __init__(self):
+ self.spam = 'eggs'
+ app = flask.Flask(__name__)
+ app.request_globals_class = CustomRequestGlobals
+ with app.test_request_context():
+ self.assert_equal(
+ flask.render_template_string('{{ g.spam }}'), 'eggs')
+
def suite():
suite = unittest.TestSuite()
From e78e2a1641e5b7ad538d93154ee59445f4d4eaf7 Mon Sep 17 00:00:00 2001
From: Ron DuPlain
Date: Tue, 24 Apr 2012 02:10:16 -0400
Subject: [PATCH 118/142] Document example request_globals_class use cases.
---
flask/app.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/flask/app.py b/flask/app.py
index 1a2961e177..aa64f4c6e8 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -150,6 +150,13 @@ class Flask(_PackageBoundObject):
#: The class that is used for the :data:`~flask.g` instance.
#:
+ #: Example use cases for a custom class:
+ #:
+ #: 1. Store arbitrary attributes on flask.g.
+ #: 2. Add a property for lazy per-request database connectors.
+ #: 3. Return None instead of AttributeError on expected attributes.
+ #: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g.
+ #:
#: .. versionadded:: 0.9
request_globals_class = _RequestGlobals
From 12dcba8849d153c7e13e99b6bcf57922e1a97240 Mon Sep 17 00:00:00 2001
From: ekoka
Date: Tue, 24 Apr 2012 05:32:52 -0300
Subject: [PATCH 119/142] Update flask/testsuite/basic.py
---
flask/testsuite/basic.py | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py
index 0a4b1d9c84..c2acca5797 100644
--- a/flask/testsuite/basic.py
+++ b/flask/testsuite/basic.py
@@ -911,6 +911,29 @@ def something_else():
self.assert_equal(c.get('/de/').data, '/de/about')
self.assert_equal(c.get('/de/about').data, '/foo')
self.assert_equal(c.get('/foo').data, '/en/about')
+
+ def test_inject_blueprint_url_defaults(self):
+ app = flask.Flask(__name__)
+ bp = flask.Blueprint('foo.bar.baz', __name__,
+ template_folder='template')
+
+ @bp.url_defaults
+ def bp_defaults(endpoint, values):
+ values['page'] = 'login'
+ @bp.route('/')
+ def view(page): pass
+
+ app.register_blueprint(bp)
+
+ values = dict()
+ app.inject_url_defaults('foo.bar.baz.view', values)
+ expected = dict(page='login')
+ self.assert_equal(values, expected)
+
+ with app.test_request_context('/somepage'):
+ url = flask.url_for('foo.bar.baz.view')
+ expected = '/login'
+ self.assert_equal(url, expected)
def test_debug_mode_complains_after_first_request(self):
app = flask.Flask(__name__)
From 2053d04db0f303430f5f6c5bc6e97b8bec46c399 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Tue, 8 May 2012 11:56:11 +0100
Subject: [PATCH 120/142] Improved interface for the URL build error handler
---
flask/app.py | 43 +++++++++++++++++++++-------------------
flask/helpers.py | 5 +++--
flask/testsuite/basic.py | 6 +++---
3 files changed, 29 insertions(+), 25 deletions(-)
diff --git a/flask/app.py b/flask/app.py
index aa64f4c6e8..40090ab0d1 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -19,7 +19,7 @@
from functools import update_wrapper
from werkzeug.datastructures import ImmutableDict
-from werkzeug.routing import Map, Rule, RequestRedirect
+from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
from werkzeug.exceptions import HTTPException, InternalServerError, \
MethodNotAllowed, BadRequest
@@ -341,16 +341,14 @@ def __init__(self, import_name, static_path=None, static_url_path=None,
#: decorator.
self.error_handler_spec = {None: self._error_handlers}
- #: If not `None`, this function is called when :meth:`url_for` raises
- #: :exc:`~werkzeug.routing.BuildError`, with the call signature::
- #:
- #: self.build_error_handler(error, endpoint, **values)
- #:
- #: Here, `error` is the instance of `BuildError`, and `endpoint` and
- #: `**values` are the arguments passed into :meth:`url_for`.
+ #: A list of functions that are called when :meth:`url_for` raises a
+ #: :exc:`~werkzeug.routing.BuildError`. Each function registered here
+ #: is called with `error`, `endpoint` and `values`. If a function
+ #: returns `None` or raises a `BuildError` the next function is
+ #: tried.
#:
#: .. versionadded:: 0.9
- self.build_error_handler = None
+ self.url_build_error_handlers = []
#: A dictionary with lists of functions that should be called at the
#: beginning of the request. The key of the dictionary is the name of
@@ -1490,19 +1488,24 @@ def inject_url_defaults(self, endpoint, values):
for func in funcs:
func(endpoint, values)
- def handle_build_error(self, error, endpoint, **values):
+ def handle_url_build_error(self, error, endpoint, values):
"""Handle :class:`~werkzeug.routing.BuildError` on :meth:`url_for`.
-
- Calls :attr:`build_error_handler` if it is not `None`.
"""
- if self.build_error_handler is None:
- exc_type, exc_value, tb = sys.exc_info()
- if exc_value is error:
- # exception is current, raise in context of original traceback.
- raise exc_type, exc_value, tb
- else:
- raise error
- return self.build_error_handler(error, endpoint, **values)
+ exc_type, exc_value, tb = sys.exc_info()
+ for handler in self.url_build_error_handlers:
+ try:
+ rv = handler(error, endpoint, values)
+ if rv is not None:
+ return rv
+ except BuildError, error:
+ pass
+
+ # At this point we want to reraise the exception. If the error is
+ # still the same one we can reraise it with the original traceback,
+ # otherwise we raise it from here.
+ if error is exc_value:
+ raise exc_type, exc_value, tb
+ raise error
def preprocess_request(self):
"""Called before the actual request dispatching and will
diff --git a/flask/helpers.py b/flask/helpers.py
index 05f84ef72b..e633e1b929 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -11,7 +11,6 @@
from __future__ import with_statement
-import imp
import os
import sys
import pkgutil
@@ -303,10 +302,12 @@ def external_url_handler(error, endpoint, **values):
rv = url_adapter.build(endpoint, values, method=method,
force_external=external)
except BuildError, error:
+ # We need to inject the values again so that the app callback can
+ # deal with that sort of stuff.
values['_external'] = external
values['_anchor'] = anchor
values['_method'] = method
- return appctx.app.handle_build_error(error, endpoint, **values)
+ return appctx.app.handle_url_build_error(error, endpoint, values)
rv = url_adapter.build(endpoint, values, method=method,
force_external=external)
diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py
index cf7590cbfe..55b66f785e 100644
--- a/flask/testsuite/basic.py
+++ b/flask/testsuite/basic.py
@@ -712,13 +712,13 @@ def test_build_error_handler(self):
try:
raise RuntimeError('Test case where BuildError is not current.')
except RuntimeError:
- self.assertRaises(BuildError, app.handle_build_error, error, 'spam')
+ self.assertRaises(BuildError, app.handle_url_build_error, error, 'spam', {})
# Test a custom handler.
- def handler(error, endpoint, **values):
+ def handler(error, endpoint, values):
# Just a test.
return '/test_handler/'
- app.build_error_handler = handler
+ app.url_build_error_handlers.append(handler)
with app.test_request_context():
self.assert_equal(flask.url_for('spam'), '/test_handler/')
From dbfd406a21191c26c2987ffd11f7c49b8733cd82 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Tue, 8 May 2012 12:51:26 +0100
Subject: [PATCH 121/142] Added required_methods
---
CHANGES | 2 ++
docs/api.rst | 4 ++++
flask/app.py | 9 ++++++++-
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/CHANGES b/CHANGES
index 10cd1c08a4..2c3f904a75 100644
--- a/CHANGES
+++ b/CHANGES
@@ -68,6 +68,8 @@ Relase date to be decided, codename to be chosen.
longer arguments to the response object, they now have a defined meaning.
- Added :attr:`flask.Flask.request_globals_class` to allow a specific class to
be used on creation of the :data:`~flask.g` instance of each request.
+- Added `required_methods` attribute to view functions to force-add methods
+ on registration.
Version 0.8.1
-------------
diff --git a/docs/api.rst b/docs/api.rst
index c329852e29..c97a69288e 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -636,6 +636,10 @@ some defaults to :meth:`~flask.Flask.add_url_rule` or general behavior:
decorators that want to customize the `OPTIONS` response on a per-view
basis.
+- `required_methods`: if this attribute is set, Flask will always add
+ these methods when registering a URL rule even if the methods were
+ explicitly overriden in the ``route()`` call.
+
Full example::
def index():
diff --git a/flask/app.py b/flask/app.py
index 40090ab0d1..a91b0f601c 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -915,6 +915,10 @@ def index():
# a tuple of only `GET` as default.
if methods is None:
methods = getattr(view_func, 'methods', None) or ('GET',)
+ methods = set(methods)
+
+ # Methods that should always be added
+ required_methods = set(getattr(view_func, 'required_methods', ()))
# starting with Flask 0.8 the view_func object can disable and
# force-enable the automatic options handling.
@@ -923,11 +927,14 @@ def index():
if provide_automatic_options is None:
if 'OPTIONS' not in methods:
- methods = tuple(methods) + ('OPTIONS',)
provide_automatic_options = True
+ required_methods.add('OPTIONS')
else:
provide_automatic_options = False
+ # Add the required methods now.
+ methods |= required_methods
+
# due to a werkzeug bug we need to make sure that the defaults are
# None if they are an empty dictionary. This should not be necessary
# with Werkzeug 0.7
From 086348e2f2874fb701048d5e1390cfe674de1f70 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Tue, 8 May 2012 13:14:32 +0100
Subject: [PATCH 122/142] Added after_this_request decorator.
---
CHANGES | 1 +
docs/api.rst | 2 ++
flask/__init__.py | 3 ++-
flask/app.py | 2 +-
flask/ctx.py | 30 ++++++++++++++++++++++++++++++
flask/testsuite/basic.py | 15 +++++++++++++++
6 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/CHANGES b/CHANGES
index 2c3f904a75..28a0790c54 100644
--- a/CHANGES
+++ b/CHANGES
@@ -70,6 +70,7 @@ Relase date to be decided, codename to be chosen.
be used on creation of the :data:`~flask.g` instance of each request.
- Added `required_methods` attribute to view functions to force-add methods
on registration.
+- Added :func:`flask.after_this_request`.
Version 0.8.1
-------------
diff --git a/docs/api.rst b/docs/api.rst
index c97a69288e..dcb54bafc9 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -289,6 +289,8 @@ Useful Functions and Classes
.. autofunction:: make_response
+.. autofunction:: after_this_request
+
.. autofunction:: send_file
.. autofunction:: send_from_directory
diff --git a/flask/__init__.py b/flask/__init__.py
index b91f9395d9..de84bb690b 100644
--- a/flask/__init__.py
+++ b/flask/__init__.py
@@ -25,7 +25,8 @@
get_template_attribute, make_response, safe_join
from .globals import current_app, g, request, session, _request_ctx_stack, \
_app_ctx_stack
-from .ctx import has_request_context, has_app_context
+from .ctx import has_request_context, has_app_context, \
+ after_this_request
from .module import Module
from .blueprints import Blueprint
from .templating import render_template, render_template_string
diff --git a/flask/app.py b/flask/app.py
index a91b0f601c..9254c398fd 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -1555,7 +1555,7 @@ def process_response(self, response):
"""
ctx = _request_ctx_stack.top
bp = ctx.request.blueprint
- funcs = ()
+ funcs = ctx._after_request_functions
if bp is not None and bp in self.after_request_funcs:
funcs = reversed(self.after_request_funcs[bp])
if None in self.after_request_funcs:
diff --git a/flask/ctx.py b/flask/ctx.py
index cf197d05dd..52eddcb218 100644
--- a/flask/ctx.py
+++ b/flask/ctx.py
@@ -11,6 +11,7 @@
import sys
+from functools import partial
from werkzeug.exceptions import HTTPException
from .globals import _request_ctx_stack, _app_ctx_stack
@@ -30,6 +31,31 @@ def _push_app_if_necessary(app):
return ctx
+def after_this_request(f):
+ """Executes a function after this request. This is useful to modify
+ response objects. The function is passed the response object and has
+ to return the same or a new one.
+
+ Example::
+
+ @app.route('/')
+ def index():
+ @after_this_request
+ def add_header():
+ response.headers['X-Foo'] = 'Parachute'
+ return response
+ return 'Hello World!'
+
+ This is more useful if a function other than the view function wants to
+ modify a response. For instance think of a decorator that wants to add
+ some headers without converting the return value into a response object.
+
+ .. versionadded:: 0.9
+ """
+ _request_ctx_stack.top._after_request_functions.append(f)
+ return f
+
+
def has_request_context():
"""If you have code that wants to test if a request context is there or
not this function can be used. For instance, you may want to take advantage
@@ -153,6 +179,10 @@ def __init__(self, app, environ):
# context, it will be stored there
self._pushed_application_context = None
+ # Functions that should be executed after the request on the response
+ # object. These will even be called in case of an error.
+ self._after_request_functions = []
+
self.match_request()
# XXX: Support for deprecated functionality. This is going away with
diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py
index 55b66f785e..ba6c2705b8 100644
--- a/flask/testsuite/basic.py
+++ b/flask/testsuite/basic.py
@@ -411,6 +411,21 @@ def index():
self.assert_('after' in evts)
self.assert_equal(rv, 'request|after')
+ def test_after_request_processing(self):
+ app = flask.Flask(__name__)
+ app.testing = True
+ @app.route('/')
+ def index():
+ @flask.after_this_request
+ def foo(response):
+ response.headers['X-Foo'] = 'a header'
+ return response
+ return 'Test'
+ c = app.test_client()
+ resp = c.get('/')
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.headers['X-Foo'], 'a header')
+
def test_teardown_request_handler(self):
called = []
app = flask.Flask(__name__)
From 444698d42b6cd2bb356d3ac6a7fa9f2be7e0df55 Mon Sep 17 00:00:00 2001
From: Alex Vykalyuk
Date: Mon, 14 May 2012 23:39:27 +0300
Subject: [PATCH 123/142] Changed docstring according to docs.
---
scripts/flaskext_compat.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/scripts/flaskext_compat.py b/scripts/flaskext_compat.py
index 2f58ccc42d..cb0b436cea 100644
--- a/scripts/flaskext_compat.py
+++ b/scripts/flaskext_compat.py
@@ -9,6 +9,7 @@
Usage::
import flaskext_compat
+ flaskext_compat.activate()
from flask.ext import foo
:copyright: (c) 2011 by Armin Ronacher.
From 447afc3525b009ed369943e13437d98e07898bdc Mon Sep 17 00:00:00 2001
From: Marc Abramowitz
Date: Sun, 27 May 2012 18:02:54 -0700
Subject: [PATCH 124/142] Fix failing test: "AssertionError:
'application/javascript' != 'application/json'" in
flask/testsuite/helpers.py", line 88
---
flask/helpers.py | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/flask/helpers.py b/flask/helpers.py
index e633e1b929..72a961a827 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -133,13 +133,17 @@ def get_current_user():
"""
if __debug__:
_assert_have_json()
+
+ padded = kwargs.get('padded', False)
if 'padded' in kwargs:
- if isinstance(kwargs['padded'], str):
- callback = request.args.get(kwargs['padded']) or 'jsonp'
+ del kwargs['padded']
+
+ if padded:
+ if isinstance(padded, str):
+ callback = request.args.get(padded) or 'jsonp'
else:
callback = request.args.get('callback') or \
request.args.get('jsonp') or 'jsonp'
- del kwargs['padded']
json_str = json.dumps(dict(*args, **kwargs), indent=None)
content = str(callback) + "(" + json_str + ")"
return current_app.response_class(content, mimetype='application/javascript')
From 2c8cbeb0c0b577f8588aa832c4bb763b8e5b3b82 Mon Sep 17 00:00:00 2001
From: Marc Abramowitz
Date: Sun, 27 May 2012 18:31:07 -0700
Subject: [PATCH 125/142] Add .travis.yml
---
.travis.yml | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 .travis.yml
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000000..d02cae024d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,11 @@
+language: python
+
+python:
+ - 2.5
+ - 2.6
+ - 2.7
+ - pypy
+
+before_install: pip install simplejson
+
+script: python setup.py test
From 99aaacb1a99891aa2eb57f6fe9b8a0c3f87cb454 Mon Sep 17 00:00:00 2001
From: Natan L
Date: Wed, 30 May 2012 20:23:02 -0700
Subject: [PATCH 126/142] Emended extensiondev.rst.
---
docs/extensiondev.rst | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst
index 59ca76c57e..d266e1a2f0 100644
--- a/docs/extensiondev.rst
+++ b/docs/extensiondev.rst
@@ -47,10 +47,10 @@ registered on PyPI. Also the development checkout link should work so
that people can easily install the development version into their
virtualenv without having to download the library by hand.
-Flask extensions must be licensed as BSD or MIT or a more liberal license
-to be enlisted on the Flask Extension Registry. Keep in mind that the
-Flask Extension Registry is a moderated place and libraries will be
-reviewed upfront if they behave as required.
+Flask extensions must be licensed under a BSD, MIT or more liberal license
+to be able to be enlisted in the Flask Extension Registry. Keep in mind
+that the Flask Extension Registry is a moderated place and libraries will
+be reviewed upfront if they behave as required.
"Hello Flaskext!"
-----------------
From 5c2aa7a9210acb4b16b86d5cda5264a2f5d11aa2 Mon Sep 17 00:00:00 2001
From: Ben Rousch
Date: Tue, 12 Jun 2012 14:26:10 -0300
Subject: [PATCH 127/142] Added link to extensions in "Hook. Extend." section.
---
docs/becomingbig.rst | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/docs/becomingbig.rst b/docs/becomingbig.rst
index ca8030603b..8d53162013 100644
--- a/docs/becomingbig.rst
+++ b/docs/becomingbig.rst
@@ -25,8 +25,9 @@ The :ref:`api` docs are full of available overrides, hook points, and
response objects. Dig deeper on the APIs you use, and look for the
customizations which are available out of the box in a Flask release. Look for
ways in which your project can be refactored into a collection of utilities and
-Flask extensions. Explore the many extensions in the community, and look for
-patterns to build your own extensions if you do not find the tools you need.
+Flask extensions. Explore the many `extensions
+` in the community, and look for patterns to
+build your own extensions if you do not find the tools you need.
Subclass.
---------
From 4b21e2d38ccf66f236c5f1c8a10bd9d7904f2599 Mon Sep 17 00:00:00 2001
From: Massimo Santini
Date: Wed, 13 Jun 2012 16:43:34 +0300
Subject: [PATCH 128/142] I think it should check that cache_timeout is not
None to allow for a (I hope legale) value of 0 for such parameter.
---
flask/helpers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flask/helpers.py b/flask/helpers.py
index 72a961a827..18502a53f0 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -533,7 +533,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
rv.cache_control.public = True
if cache_timeout is None:
cache_timeout = current_app.get_send_file_max_age(filename)
- if cache_timeout:
+ if cache_timeout is not None:
rv.cache_control.max_age = cache_timeout
rv.expires = int(time() + cache_timeout)
From 5bbf8bdcd9619fa15a3380f99fe6d66d08b1f783 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ramiro=20G=C3=B3mez?=
Date: Sun, 17 Jun 2012 02:22:02 +0300
Subject: [PATCH 129/142] Update master
---
docs/deploying/fastcgi.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst
index b280156072..0e2f6cdc0b 100644
--- a/docs/deploying/fastcgi.rst
+++ b/docs/deploying/fastcgi.rst
@@ -102,7 +102,7 @@ Set yourapplication.fcgi::
def __call__(self, environ, start_response):
environ['SCRIPT_NAME'] = ''
- return self.app(environ, start_response)
+ return self.app(environ, start_response)
app = ScriptNameStripper(app)
From 6809ffccf2b8b94633717f839b42ae8e3fbca1c3 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Sun, 17 Jun 2012 14:13:53 +0100
Subject: [PATCH 130/142] Don't build websites with travis
---
.travis.yml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.travis.yml b/.travis.yml
index d02cae024d..8cd434d1b2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,3 +9,7 @@ python:
before_install: pip install simplejson
script: python setup.py test
+
+branches:
+ except:
+ - website
From b04827283ea36cc456367f1ac4e0700b90b71283 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Sun, 17 Jun 2012 14:17:22 +0100
Subject: [PATCH 131/142] Removed padded JSON (JSONP) again.
The implementation was not clean and generally the needs for padded json
are disappearing now that all browsers support cross site communication
with the regular xmlhttprequest.
---
flask/helpers.py | 26 --------------------------
flask/testsuite/helpers.py | 19 +------------------
2 files changed, 1 insertion(+), 44 deletions(-)
diff --git a/flask/helpers.py b/flask/helpers.py
index 18502a53f0..71bc3142e2 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -118,35 +118,9 @@ def get_current_user():
information about this, have a look at :ref:`json-security`.
.. versionadded:: 0.2
-
- .. versionadded:: 0.9
- If the ``padded`` argument is true, the JSON object will be padded
- for JSONP calls and the response mimetype will be changed to
- ``application/javascript``. By default, the request arguments ``callback``
- and ``jsonp`` will be used as the name for the callback function.
- This will work with jQuery and most other JavaScript libraries
- by default.
-
- If the ``padded`` argument is a string, jsonify will look for
- the request argument with the same name and use that value as the
- callback-function name.
"""
if __debug__:
_assert_have_json()
-
- padded = kwargs.get('padded', False)
- if 'padded' in kwargs:
- del kwargs['padded']
-
- if padded:
- if isinstance(padded, str):
- callback = request.args.get(padded) or 'jsonp'
- else:
- callback = request.args.get('callback') or \
- request.args.get('jsonp') or 'jsonp'
- json_str = json.dumps(dict(*args, **kwargs), indent=None)
- content = str(callback) + "(" + json_str + ")"
- return current_app.response_class(content, mimetype='application/javascript')
return current_app.response_class(json.dumps(dict(*args, **kwargs),
indent=None if request.is_xhr else 2), mimetype='application/json')
diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py
index a0e60aac77..816f6cd8bc 100644
--- a/flask/testsuite/helpers.py
+++ b/flask/testsuite/helpers.py
@@ -73,28 +73,11 @@ def return_kwargs():
@app.route('/dict')
def return_dict():
return flask.jsonify(d)
- @app.route("/unpadded")
- def return_padded_false():
- return flask.jsonify(d, padded=False)
- @app.route("/padded")
- def return_padded_true():
- return flask.jsonify(d, padded=True)
- @app.route("/padded_custom")
- def return_padded_json_custom_callback():
- return flask.jsonify(d, padded='my_func_name')
c = app.test_client()
- for url in '/kw', '/dict', '/unpadded':
+ for url in '/kw', '/dict':
rv = c.get(url)
self.assert_equal(rv.mimetype, 'application/json')
self.assert_equal(flask.json.loads(rv.data), d)
- for get_arg in 'callback=funcName', 'jsonp=funcName':
- rv = c.get('/padded?' + get_arg)
- self.assert_( rv.data.startswith("funcName(") )
- self.assert_( rv.data.endswith(")") )
- rv_json = rv.data.split('(')[1].split(')')[0]
- self.assert_equal(flask.json.loads(rv_json), d)
- rv = c.get('/padded_custom?my_func_name=funcName')
- self.assert_( rv.data.startswith("funcName(") )
def test_json_attr(self):
app = flask.Flask(__name__)
From 7b1c8fd15b619aee9eb6bc53dd50bd2d7e9bea3d Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Sun, 17 Jun 2012 14:22:15 +0100
Subject: [PATCH 132/142] Added #522 in modified version
---
docs/deploying/cgi.rst | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/docs/deploying/cgi.rst b/docs/deploying/cgi.rst
index a2fba90d93..1de9bd2c83 100644
--- a/docs/deploying/cgi.rst
+++ b/docs/deploying/cgi.rst
@@ -35,12 +35,23 @@ Usually there are two ways to configure the server. Either just copy the
`.cgi` into a `cgi-bin` (and use `mod_rewrite` or something similar to
rewrite the URL) or let the server point to the file directly.
-In Apache for example you can put a like like this into the config:
+In Apache for example you can put something like this into the config:
.. sourcecode:: apache
ScriptAlias /app /path/to/the/application.cgi
+On shared webhosting, though, you might not have access to your Apache config.
+In this case, a file called `.htaccess`, sitting in the public directory you want
+your app to be available, works too but the `ScriptAlias` directive won't
+work in that case:
+
+.. sourcecode:: apache
+
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-f # Don't interfere with static files
+ RewriteRule ^(.*)$ /path/to/the/application.cgi/$1 [L]
+
For more information consult the documentation of your webserver.
.. _App Engine: http://code.google.com/appengine/
From 1f3e667b5d9ffb60c218c250df27144793a5acdb Mon Sep 17 00:00:00 2001
From: Matt Wright
Date: Mon, 18 Jun 2012 18:33:17 -0300
Subject: [PATCH 133/142] Fix documention for `after_this_request`
---
flask/ctx.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flask/ctx.py b/flask/ctx.py
index 52eddcb218..f64ab04a02 100644
--- a/flask/ctx.py
+++ b/flask/ctx.py
@@ -41,7 +41,7 @@ def after_this_request(f):
@app.route('/')
def index():
@after_this_request
- def add_header():
+ def add_header(response):
response.headers['X-Foo'] = 'Parachute'
return response
return 'Hello World!'
From 1f82d02b33dad8ac7a4aa49e690767027690fe1a Mon Sep 17 00:00:00 2001
From: bev-a-tron
Date: Mon, 25 Jun 2012 13:31:11 -0400
Subject: [PATCH 134/142] Fixes #519 by adding return statement
---
docs/quickstart.rst | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index e8b71ca98a..53ef38d4f3 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -514,8 +514,9 @@ attributes mentioned above::
return log_the_user_in(request.form['username'])
else:
error = 'Invalid username/password'
- # this is executed if the request method was GET or the
- # credentials were invalid
+ # the code below this is executed if the request method
+ # was GET or the credentials were invalid
+ return render_template('login.html', error=error)
What happens if the key does not exist in the `form` attribute? In that
case a special :exc:`KeyError` is raised. You can catch it like a
From 8071f11328ab2b767608c5c63d0bad72d9408120 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Tue, 26 Jun 2012 17:18:59 +0200
Subject: [PATCH 135/142] Fixed an issue with the new path finding logic
---
flask/helpers.py | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/flask/helpers.py b/flask/helpers.py
index e633e1b929..f714a699f7 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -616,17 +616,29 @@ def get_root_path(import_name):
Not to be confused with the package path returned by :func:`find_package`.
"""
+ # Module already imported and has a file attribute. Use that first.
+ mod = sys.modules.get(import_name)
+ if mod is not None and hasattr(mod, '__file__'):
+ return os.path.dirname(os.path.abspath(mod.__file__))
+
+ # Next attempt: check the loader.
loader = pkgutil.get_loader(import_name)
+
+ # Loader does not exist or we're referring to an unloaded main module
+ # or a main module without path (interactive sessions), go with the
+ # current working directory.
if loader is None or import_name == '__main__':
- # import name is not found, or interactive/main module
return os.getcwd()
+
# For .egg, zipimporter does not have get_filename until Python 2.7.
+ # Some other loaders might exhibit the same behavior.
if hasattr(loader, 'get_filename'):
filepath = loader.get_filename(import_name)
else:
# Fall back to imports.
__import__(import_name)
filepath = sys.modules[import_name].__file__
+
# filepath is import_name.py for a module, or __init__.py for a package.
return os.path.dirname(os.path.abspath(filepath))
From 558750494f6f99b1272218c35d86e6452484e77c Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Wed, 27 Jun 2012 12:08:01 +0100
Subject: [PATCH 136/142] Removed unnecessary import
---
flask/ctx.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/flask/ctx.py b/flask/ctx.py
index 52eddcb218..3f3fbc526e 100644
--- a/flask/ctx.py
+++ b/flask/ctx.py
@@ -11,7 +11,6 @@
import sys
-from functools import partial
from werkzeug.exceptions import HTTPException
from .globals import _request_ctx_stack, _app_ctx_stack
From 43c6a1ede87da5c20d8b5c7db995cf2d22eb40b2 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Wed, 27 Jun 2012 12:22:39 +0100
Subject: [PATCH 137/142] Fixed a comment
---
flask/ctx.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/flask/ctx.py b/flask/ctx.py
index 3f3fbc526e..90858aa4fa 100644
--- a/flask/ctx.py
+++ b/flask/ctx.py
@@ -179,7 +179,8 @@ def __init__(self, app, environ):
self._pushed_application_context = None
# Functions that should be executed after the request on the response
- # object. These will even be called in case of an error.
+ # object. These will be called before the regular "after_request"
+ # functions.
self._after_request_functions = []
self.match_request()
From d5218997d927be869dd55ef04542e1bbc1e69653 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Wed, 27 Jun 2012 15:06:39 +0100
Subject: [PATCH 138/142] Added flask.stream_with_context
---
CHANGES | 2 +
docs/api.rst | 5 +++
docs/patterns/streaming.rst | 23 ++++++++++++
flask/__init__.py | 3 +-
flask/ctx.py | 64 +++++++++++++++++++-------------
flask/helpers.py | 73 +++++++++++++++++++++++++++++++++++++
flask/testsuite/appctx.py | 20 ++++++++++
flask/testsuite/helpers.py | 59 ++++++++++++++++++++++++++++++
8 files changed, 222 insertions(+), 27 deletions(-)
diff --git a/CHANGES b/CHANGES
index 28a0790c54..acdfee6918 100644
--- a/CHANGES
+++ b/CHANGES
@@ -71,6 +71,8 @@ Relase date to be decided, codename to be chosen.
- Added `required_methods` attribute to view functions to force-add methods
on registration.
- Added :func:`flask.after_this_request`.
+- Added :func:`flask.stream_with_context` and the ability to push contexts
+ multiple times without producing unexpected behavior.
Version 0.8.1
-------------
diff --git a/docs/api.rst b/docs/api.rst
index dcb54bafc9..8a7b5ce061 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -375,6 +375,11 @@ Extensions
.. versionadded:: 0.8
+Stream Helpers
+--------------
+
+.. autofunction:: stream_with_context
+
Useful Internals
----------------
diff --git a/docs/patterns/streaming.rst b/docs/patterns/streaming.rst
index 8393b00b6e..ac232dcc0c 100644
--- a/docs/patterns/streaming.rst
+++ b/docs/patterns/streaming.rst
@@ -59,3 +59,26 @@ The template is then evaluated as the stream is iterated over. Since each
time you do a yield the server will flush the content to the client you
might want to buffer up a few items in the template which you can do with
``rv.enable_buffering(size)``. ``5`` is a sane default.
+
+Streaming with Context
+----------------------
+
+.. versionadded:: 0.9
+
+Note that when you stream data, the request context is already gone the
+moment the function executes. Flask 0.9 provides you with a helper that
+can keep the request context around during the execution of the
+generator::
+
+ from flask import stream_with_context, request, Response
+
+ @app.route('/stream')
+ def streamed_response():
+ def generate():
+ yield 'Hello '
+ yield request.args['name']
+ yield '!'
+ return Response(stream_with_context(generate()))
+
+Without the :func:`~flask.stream_with_context` function you would get a
+:class:`RuntimeError` at that point.
diff --git a/flask/__init__.py b/flask/__init__.py
index de84bb690b..e48f7a97dd 100644
--- a/flask/__init__.py
+++ b/flask/__init__.py
@@ -22,7 +22,8 @@
from .config import Config
from .helpers import url_for, jsonify, json_available, flash, \
send_file, send_from_directory, get_flashed_messages, \
- get_template_attribute, make_response, safe_join
+ get_template_attribute, make_response, safe_join, \
+ stream_with_context
from .globals import current_app, g, request, session, _request_ctx_stack, \
_app_ctx_stack
from .ctx import has_request_context, has_app_context, \
diff --git a/flask/ctx.py b/flask/ctx.py
index 0cf34491b1..3ea42a279b 100644
--- a/flask/ctx.py
+++ b/flask/ctx.py
@@ -22,14 +22,6 @@ class _RequestGlobals(object):
pass
-def _push_app_if_necessary(app):
- top = _app_ctx_stack.top
- if top is None or top.app != app:
- ctx = app.app_context()
- ctx.push()
- return ctx
-
-
def after_this_request(f):
"""Executes a function after this request. This is useful to modify
response objects. The function is passed the response object and has
@@ -110,15 +102,22 @@ def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
+ # Like request context, app contexts can be pushed multiple times
+ # but there a basic "refcount" is enough to track them.
+ self._refcnt = 0
+
def push(self):
"""Binds the app context to the current context."""
+ self._refcnt += 1
_app_ctx_stack.push(self)
def pop(self, exc=None):
"""Pops the app context."""
- if exc is None:
- exc = sys.exc_info()[1]
- self.app.do_teardown_appcontext(exc)
+ self._refcnt -= 1
+ if self._refcnt <= 0:
+ if exc is None:
+ exc = sys.exc_info()[1]
+ self.app.do_teardown_appcontext(exc)
rv = _app_ctx_stack.pop()
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
% (rv, self)
@@ -128,7 +127,7 @@ def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
- self.pop()
+ self.pop(exc_value)
class RequestContext(object):
@@ -169,15 +168,16 @@ def __init__(self, app, environ):
self.flashes = None
self.session = None
+ # Request contexts can be pushed multiple times and interleaved with
+ # other request contexts. Now only if the last level is popped we
+ # get rid of them. Additionally if an application context is missing
+ # one is created implicitly so for each level we add this information
+ self._implicit_app_ctx_stack = []
+
# indicator if the context was preserved. Next time another context
# is pushed the preserved context is popped.
self.preserved = False
- # Indicates if pushing this request context also triggered the pushing
- # of an application context. If it implicitly pushed an application
- # context, it will be stored there
- self._pushed_application_context = None
-
# Functions that should be executed after the request on the response
# object. These will be called before the regular "after_request"
# functions.
@@ -222,7 +222,13 @@ def push(self):
# Before we push the request context we have to ensure that there
# is an application context.
- self._pushed_application_context = _push_app_if_necessary(self.app)
+ app_ctx = _app_ctx_stack.top
+ if app_ctx is None or app_ctx.app != self.app:
+ app_ctx = self.app.app_context()
+ app_ctx.push()
+ self._implicit_app_ctx_stack.append(app_ctx)
+ else:
+ self._implicit_app_ctx_stack.append(None)
_request_ctx_stack.push(self)
@@ -241,22 +247,28 @@ def pop(self, exc=None):
.. versionchanged:: 0.9
Added the `exc` argument.
"""
- self.preserved = False
- if exc is None:
- exc = sys.exc_info()[1]
- self.app.do_teardown_request(exc)
+ app_ctx = self._implicit_app_ctx_stack.pop()
+
+ clear_request = False
+ if not self._implicit_app_ctx_stack:
+ self.preserved = False
+ if exc is None:
+ exc = sys.exc_info()[1]
+ self.app.do_teardown_request(exc)
+ clear_request = True
+
rv = _request_ctx_stack.pop()
assert rv is self, 'Popped wrong request context. (%r instead of %r)' \
% (rv, self)
# get rid of circular dependencies at the end of the request
# so that we don't require the GC to be active.
- rv.request.environ['werkzeug.request'] = None
+ if clear_request:
+ rv.request.environ['werkzeug.request'] = None
# Get rid of the app as well if necessary.
- if self._pushed_application_context:
- self._pushed_application_context.pop(exc)
- self._pushed_application_context = None
+ if app_ctx is not None:
+ app_ctx.pop(exc)
def __enter__(self):
self.push()
diff --git a/flask/helpers.py b/flask/helpers.py
index 631e29bed2..501a2f811c 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -21,6 +21,7 @@
from threading import RLock
from werkzeug.routing import BuildError
from werkzeug.urls import url_quote
+from functools import update_wrapper
# try to load the best simplejson implementation available. If JSON
# is not installed, we add a failing class.
@@ -92,6 +93,78 @@ def _endpoint_from_view_func(view_func):
return view_func.__name__
+def stream_with_context(generator_or_function):
+ """Request contexts disappear when the response is started on the server.
+ This is done for efficiency reasons and to make it less likely to encounter
+ memory leaks with badly written WSGI middlewares. The downside is that if
+ you are using streamed responses, the generator cannot access request bound
+ information any more.
+
+ This function however can help you keep the context around for longer::
+
+ from flask import stream_with_context, request, Response
+
+ @app.route('/stream')
+ def streamed_response():
+ @stream_with_context
+ def generate():
+ yield 'Hello '
+ yield request.args['name']
+ yield '!'
+ return Response(generate())
+
+ Alternatively it can also be used around a specific generator:
+
+ from flask import stream_with_context, request, Response
+
+ @app.route('/stream')
+ def streamed_response():
+ def generate():
+ yield 'Hello '
+ yield request.args['name']
+ yield '!'
+ return Response(stream_with_context(generate()))
+
+ .. versionadded:: 0.9
+ """
+ try:
+ gen = iter(generator_or_function)
+ except TypeError:
+ def decorator(*args, **kwargs):
+ gen = generator_or_function()
+ return stream_with_context(gen)
+ return update_wrapper(decorator, generator_or_function)
+
+ def generator():
+ ctx = _request_ctx_stack.top
+ if ctx is None:
+ raise RuntimeError('Attempted to stream with context but '
+ 'there was no context in the first place to keep around.')
+ with ctx:
+ # Dummy sentinel. Has to be inside the context block or we're
+ # not actually keeping the context around.
+ yield None
+
+ # The try/finally is here so that if someone passes a WSGI level
+ # iterator in we're still running the cleanup logic. Generators
+ # don't need that because they are closed on their destruction
+ # automatically.
+ try:
+ for item in gen:
+ yield item
+ finally:
+ if hasattr(gen, 'close'):
+ gen.close()
+
+ # The trick is to start the generator. Then the code execution runs until
+ # the first dummy None is yielded at which point the context was already
+ # pushed. This item is discarded. Then when the iteration continues the
+ # real generator is executed.
+ wrapped_g = generator()
+ wrapped_g.next()
+ return wrapped_g
+
+
def jsonify(*args, **kwargs):
"""Creates a :class:`~flask.Response` with the JSON representation of
the given arguments with an `application/json` mimetype. The arguments
diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py
index 1dcdb406f1..6454389e8b 100644
--- a/flask/testsuite/appctx.py
+++ b/flask/testsuite/appctx.py
@@ -75,6 +75,26 @@ def __init__(self):
self.assert_equal(
flask.render_template_string('{{ g.spam }}'), 'eggs')
+ def test_context_refcounts(self):
+ called = []
+ app = flask.Flask(__name__)
+ @app.teardown_request
+ def teardown_req(error=None):
+ called.append('request')
+ @app.teardown_appcontext
+ def teardown_app(error=None):
+ called.append('app')
+ @app.route('/')
+ def index():
+ with flask._app_ctx_stack.top:
+ with flask._request_ctx_stack.top:
+ pass
+ self.assert_(flask._request_ctx_stack.request.environ
+ ['werkzeug.request'] is not None)
+ c = app.test_client()
+ c.get('/')
+ self.assertEqual(called, ['request', 'app'])
+
def suite():
suite = unittest.TestSuite()
diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py
index 816f6cd8bc..54c014829f 100644
--- a/flask/testsuite/helpers.py
+++ b/flask/testsuite/helpers.py
@@ -397,6 +397,64 @@ def test_name_with_import_error(self):
self.fail('Flask(import_name) is importing import_name.')
+class StreamingTestCase(FlaskTestCase):
+
+ def test_streaming_with_context(self):
+ app = flask.Flask(__name__)
+ app.testing = True
+ @app.route('/')
+ def index():
+ def generate():
+ yield 'Hello '
+ yield flask.request.args['name']
+ yield '!'
+ return flask.Response(flask.stream_with_context(generate()))
+ c = app.test_client()
+ rv = c.get('/?name=World')
+ self.assertEqual(rv.data, 'Hello World!')
+
+ def test_streaming_with_context_as_decorator(self):
+ app = flask.Flask(__name__)
+ app.testing = True
+ @app.route('/')
+ def index():
+ @flask.stream_with_context
+ def generate():
+ yield 'Hello '
+ yield flask.request.args['name']
+ yield '!'
+ return flask.Response(generate())
+ c = app.test_client()
+ rv = c.get('/?name=World')
+ self.assertEqual(rv.data, 'Hello World!')
+
+ def test_streaming_with_context_and_custom_close(self):
+ app = flask.Flask(__name__)
+ app.testing = True
+ called = []
+ class Wrapper(object):
+ def __init__(self, gen):
+ self._gen = gen
+ def __iter__(self):
+ return self
+ def close(self):
+ called.append(42)
+ def next(self):
+ return self._gen.next()
+ @app.route('/')
+ def index():
+ def generate():
+ yield 'Hello '
+ yield flask.request.args['name']
+ yield '!'
+ return flask.Response(flask.stream_with_context(
+ Wrapper(generate())))
+ c = app.test_client()
+ rv = c.get('/?name=World')
+ self.assertEqual(rv.data, 'Hello World!')
+ self.assertEqual(called, [42])
+
+
def suite():
suite = unittest.TestSuite()
if flask.json_available:
@@ -404,4 +462,5 @@ def suite():
suite.addTest(unittest.makeSuite(SendfileTestCase))
suite.addTest(unittest.makeSuite(LoggingTestCase))
suite.addTest(unittest.makeSuite(NoImportsTestCase))
+ suite.addTest(unittest.makeSuite(StreamingTestCase))
return suite
From 19def9606ac50bd308ea283e283cbcf62498d6c7 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Sun, 1 Jul 2012 12:08:38 +0100
Subject: [PATCH 139/142] This is 0.8.1
---
CHANGES | 2 +-
flask/__init__.py | 2 +-
setup.py | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/CHANGES b/CHANGES
index 7640ac21ef..014d579fef 100644
--- a/CHANGES
+++ b/CHANGES
@@ -6,7 +6,7 @@ Here you can see the full list of changes between each Flask release.
Version 0.8.1
-------------
-Bugfix release, release date to be decided
+Bugfix release, released on July 1th 2012
- Fixed an issue with the undocumented `flask.session` module to not
work properly on Python 2.5. It should not be used but did cause
diff --git a/flask/__init__.py b/flask/__init__.py
index 04d7d1f2a5..98fcbe1368 100644
--- a/flask/__init__.py
+++ b/flask/__init__.py
@@ -10,7 +10,7 @@
:license: BSD, see LICENSE for more details.
"""
-__version__ = '0.8.1-dev'
+__version__ = '0.8.1'
# utilities we import from Werkzeug and Jinja2 that are unused
# in the module but are exported as public interface.
diff --git a/setup.py b/setup.py
index af36a8bb08..2aad7b5731 100644
--- a/setup.py
+++ b/setup.py
@@ -77,7 +77,7 @@ def run(self):
setup(
name='Flask',
- version='0.8.1-dev',
+ version='0.8.1',
url='http://github.com/mitsuhiko/flask/',
license='BSD',
author='Armin Ronacher',
From ee3e251f9eb557721517faa6d06a6addd48ebc24 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Sun, 1 Jul 2012 12:12:36 +0100
Subject: [PATCH 140/142] Updated CHANGES
---
CHANGES | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGES b/CHANGES
index 68ddab78f9..6d0c49252c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -6,7 +6,7 @@ Here you can see the full list of changes between each Flask release.
Version 0.9
-----------
-Relase date to be decided, codename to be chosen.
+Released on July 1st 2012, codename Camapri.
- The :func:`flask.Request.on_json_loading_failed` now returns a JSON formatted
response by default.
From 56f5224ef7cdc48a05b4ce6dcc37043feab0c0bb Mon Sep 17 00:00:00 2001
From: Laurens Van Houtven <_@lvh.cc>
Date: Sun, 1 Jul 2012 19:31:53 +0300
Subject: [PATCH 141/142] CHANGES: July 1th should be July 1st
---
CHANGES | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGES b/CHANGES
index 6d0c49252c..5889aa92a5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -77,7 +77,7 @@ Released on July 1st 2012, codename Camapri.
Version 0.8.1
-------------
-Bugfix release, released on July 1th 2012
+Bugfix release, released on July 1st 2012
- Fixed an issue with the undocumented `flask.session` module to not
work properly on Python 2.5. It should not be used but did cause
From d8e5a37d8a5e9dda1154c6c7c614bb7a4c42afdd Mon Sep 17 00:00:00 2001
From: esaurito
Date: Mon, 2 Jul 2012 00:38:27 +0300
Subject: [PATCH 142/142] Fixed codename
---
CHANGES | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGES b/CHANGES
index 5889aa92a5..7d1aeca101 100644
--- a/CHANGES
+++ b/CHANGES
@@ -6,7 +6,7 @@ Here you can see the full list of changes between each Flask release.
Version 0.9
-----------
-Released on July 1st 2012, codename Camapri.
+Released on July 1st 2012, codename Campari.
- The :func:`flask.Request.on_json_loading_failed` now returns a JSON formatted
response by default.