From 2e5de9829774ca10682b298544bf3ce2bf61bab7 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 17 Jan 2012 19:33:48 -0500 Subject: [PATCH 001/142] Use app.testing=True for asserts in messages test. --- flask/testsuite/basic.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index f543ba9f87..e6a278e53a 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -309,8 +309,15 @@ def test_flashes(self): self.assert_equal(list(flask.get_flashed_messages()), ['Zap', 'Zip']) def test_extended_flashing(self): + # Be sure app.testing=True below, else tests can fail silently. + # + # Specifically, if app.testing is not set to True, the AssertionErrors + # in the view functions will cause a 500 response to the test client + # instead of propagating exceptions. + app = flask.Flask(__name__) app.secret_key = 'testkey' + app.testing = True @app.route('/') def index(): @@ -360,33 +367,27 @@ def test_filters(): self.assert_equal(messages[1], flask.Markup(u'Testing')) return '' - # Note: if status code assertions are missing, failed tests still pass. - # - # Since app.test_client() does not set debug=True, the AssertionErrors - # in the view functions are swallowed and the only indicator is a 500 - # status code. - # - # Also, create new test client on each test to clean flashed messages. + # Create new test client on each test to clean flashed messages. c = app.test_client() c.get('/') - assert c.get('/test/').status_code == 200 + c.get('/test/') c = app.test_client() c.get('/') - assert c.get('/test_with_categories/').status_code == 200 + c.get('/test_with_categories/') c = app.test_client() c.get('/') - assert c.get('/test_filter/').status_code == 200 + c.get('/test_filter/') c = app.test_client() c.get('/') - assert c.get('/test_filters/').status_code == 200 + c.get('/test_filters/') c = app.test_client() c.get('/') - assert c.get('/test_filters_without_returning_categories/').status_code == 200 + c.get('/test_filters_without_returning_categories/') def test_request_processing(self): app = flask.Flask(__name__) From 56177bcbd16f9e73252d7af7c78d25dc60ed946d Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 17 Jan 2012 19:43:11 -0500 Subject: [PATCH 002/142] Document app.testing=True for test client, #381. --- flask/app.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/flask/app.py b/flask/app.py index ccce704668..15e432dee7 100644 --- a/flask/app.py +++ b/flask/app.py @@ -716,6 +716,17 @@ def test_client(self, use_cookies=True): """Creates a test client for this application. For information about unit testing head over to :ref:`testing`. + Note that if you are testing for assertions or exceptions in your + application code, you must set ``app.testing = True`` in order for the + exceptions to propagate to the test client. Otherwise, the exception + will be handled by the application (not visible to the test client) and + the only indication of an AssertionError or other exception will be a + 500 status code response to the test client. See the :attr:`testing` + attribute. For example:: + + app.testing = True + client = app.test_client() + The test client can be used in a `with` block to defer the closing down of the context until the end of the `with` block. This is useful if you want to access the context locals for testing:: From b786eac5574e478d51314333fb6309456bff7b76 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Wed, 18 Jan 2012 18:57:05 -0500 Subject: [PATCH 003/142] Add test for limited imp loaders, #380. --- flask/testsuite/config.py | 87 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index e0f7005f88..00f77cea21 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -8,10 +8,14 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +from __future__ import with_statement + import os import sys import flask +import pkgutil import unittest +from contextlib import contextmanager from flask.testsuite import FlaskTestCase @@ -84,6 +88,35 @@ def test_session_lifetime(self): self.assert_equal(app.permanent_session_lifetime.seconds, 42) +class LimitedLoaderMockWrapper(object): + def __init__(self, loader): + self.loader = loader + + def __getattr__(self, name): + if name in ('archive', 'get_filename'): + msg = 'Mocking a loader which does not have `%s.`' % name + raise AttributeError, msg + return getattr(self.loader, name) + + +@contextmanager +def patch_pkgutil_get_loader(wrapper_class=LimitedLoaderMockWrapper): + """Patch pkgutil.get_loader to give loader without get_filename or archive. + + This provides for tests where a system has custom loaders, e.g. Google App + Engine's HardenedModulesHook, which have neither the `get_filename` method + nor the `archive` attribute. + """ + old_get_loader = pkgutil.get_loader + def get_loader(*args, **kwargs): + return wrapper_class(old_get_loader(*args, **kwargs)) + try: + pkgutil.get_loader = get_loader + yield + finally: + pkgutil.get_loader = old_get_loader + + class InstanceTestCase(FlaskTestCase): def test_explicit_instance_paths(self): @@ -133,6 +166,24 @@ def test_installed_module_paths(self): if 'site_app' in sys.modules: del sys.modules['site_app'] + def test_installed_module_paths_with_limited_loader(self): + here = os.path.abspath(os.path.dirname(__file__)) + expected_prefix = os.path.join(here, 'test_apps') + real_prefix, sys.prefix = sys.prefix, expected_prefix + site_packages = os.path.join(expected_prefix, 'lib', 'python2.5', 'site-packages') + sys.path.append(site_packages) + with patch_pkgutil_get_loader(): + try: + import site_app + self.assert_equal(site_app.app.instance_path, + os.path.join(expected_prefix, 'var', + 'site_app-instance')) + finally: + sys.prefix = real_prefix + sys.path.remove(site_packages) + if 'site_app' in sys.modules: + del sys.modules['site_app'] + def test_installed_package_paths(self): here = os.path.abspath(os.path.dirname(__file__)) expected_prefix = os.path.join(here, 'test_apps') @@ -150,6 +201,24 @@ def test_installed_package_paths(self): if 'installed_package' in sys.modules: del sys.modules['installed_package'] + def test_installed_package_paths_with_limited_loader(self): + here = os.path.abspath(os.path.dirname(__file__)) + expected_prefix = os.path.join(here, 'test_apps') + real_prefix, sys.prefix = sys.prefix, expected_prefix + installed_path = os.path.join(expected_prefix, 'path') + sys.path.append(installed_path) + with patch_pkgutil_get_loader(): + try: + import installed_package + self.assert_equal(installed_package.app.instance_path, + os.path.join(expected_prefix, 'var', + 'installed_package-instance')) + finally: + sys.prefix = real_prefix + sys.path.remove(installed_path) + if 'installed_package' in sys.modules: + del sys.modules['installed_package'] + def test_prefix_package_paths(self): here = os.path.abspath(os.path.dirname(__file__)) expected_prefix = os.path.join(here, 'test_apps') @@ -167,6 +236,24 @@ def test_prefix_package_paths(self): if 'site_package' in sys.modules: del sys.modules['site_package'] + def test_prefix_package_paths_with_limited_loader(self): + here = os.path.abspath(os.path.dirname(__file__)) + expected_prefix = os.path.join(here, 'test_apps') + real_prefix, sys.prefix = sys.prefix, expected_prefix + site_packages = os.path.join(expected_prefix, 'lib', 'python2.5', 'site-packages') + sys.path.append(site_packages) + with patch_pkgutil_get_loader(): + try: + import site_package + self.assert_equal(site_package.app.instance_path, + os.path.join(expected_prefix, 'var', + 'site_package-instance')) + finally: + sys.prefix = real_prefix + sys.path.remove(site_packages) + if 'site_package' in sys.modules: + del sys.modules['site_package'] + def test_egg_installed_paths(self): here = os.path.abspath(os.path.dirname(__file__)) expected_prefix = os.path.join(here, 'test_apps') From 76c1a1f7227ca13f3c5952b248af93b75e9a95c9 Mon Sep 17 00:00:00 2001 From: FND Date: Mon, 23 Jan 2012 20:12:56 +0100 Subject: [PATCH 004/142] fixed spelling of "instantiate" while the interwebs suggest "instanciate" might be a valid spelling, it seems quite uncommon and potentially irritating (to pedants like myself) --- docs/patterns/appdispatch.rst | 2 +- docs/views.rst | 2 +- flask/views.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst index 93b4af96bc..177ade2bc7 100644 --- a/docs/patterns/appdispatch.rst +++ b/docs/patterns/appdispatch.rst @@ -58,7 +58,7 @@ Dispatch by Subdomain Sometimes you might want to use multiple instances of the same application with different configurations. Assuming the application is created inside -a function and you can call that function to instanciate it, that is +a function and you can call that function to instantiate it, that is really easy to implement. In order to develop your application to support creating new instances in functions have a look at the :ref:`app-factories` pattern. diff --git a/docs/views.rst b/docs/views.rst index feee0a8b64..9270921b50 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -74,7 +74,7 @@ enough to explain the basic principle. When you have a class based view the question comes up what `self` points to. The way this works is that whenever the request is dispatched a new instance of the class is created and the :meth:`~flask.views.View.dispatch_request` method is called with -the parameters from the URL rule. The class itself is instanciated with +the parameters from the URL rule. The class itself is instantiated with the parameters passed to the :meth:`~flask.views.View.as_view` function. For instance you can write a class like this:: diff --git a/flask/views.py b/flask/views.py index 3e35e46f4c..811fa19655 100644 --- a/flask/views.py +++ b/flask/views.py @@ -72,7 +72,7 @@ def dispatch_request(self): def as_view(cls, name, *class_args, **class_kwargs): """Converts the class into an actual view function that can be used with the routing system. What it does internally is generating - a function on the fly that will instanciate the :class:`View` + a function on the fly that will instantiate the :class:`View` on each request and call the :meth:`dispatch_request` method on it. The arguments passed to :meth:`as_view` are forwarded to the @@ -90,7 +90,7 @@ def view(*args, **kwargs): # we attach the view class to the view function for two reasons: # first of all it allows us to easily figure out what class based - # view this thing came from, secondly it's also used for instanciating + # view this thing came from, secondly it's also used for instantiating # the view class so you can actually replace it with something else # for testing purposes and debugging. view.view_class = cls From 2792dcf23e848472767a075e7c30e28890fd539d Mon Sep 17 00:00:00 2001 From: FND Date: Mon, 23 Jan 2012 20:27:42 +0100 Subject: [PATCH 005/142] simplified as_view documentation in the process, rewrapped lines to 78 chars (the file's current maximum) --- flask/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flask/views.py b/flask/views.py index 811fa19655..150e84c356 100644 --- a/flask/views.py +++ b/flask/views.py @@ -70,10 +70,10 @@ def dispatch_request(self): @classmethod def as_view(cls, name, *class_args, **class_kwargs): - """Converts the class into an actual view function that can be - used with the routing system. What it does internally is generating - a function on the fly that will instantiate the :class:`View` - on each request and call the :meth:`dispatch_request` method on it. + """Converts the class into an actual view function that can be used + with the routing system. Internally this generates a function on the + fly which will instantiate the :class:`View` on each request and call + the :meth:`dispatch_request` method on it. The arguments passed to :meth:`as_view` are forwarded to the constructor of the class. From c5ebf9a97d0443bff59ff6a37aee8afd870ceabb Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 24 Jan 2012 16:48:04 -0500 Subject: [PATCH 006/142] Added PATCH method to the list of HTTP method functions for use in the flask.views.MethodView class. --- flask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/views.py b/flask/views.py index 150e84c356..2aaaf156ec 100644 --- a/flask/views.py +++ b/flask/views.py @@ -12,7 +12,7 @@ http_method_funcs = frozenset(['get', 'post', 'head', 'options', - 'delete', 'put', 'trace']) + 'delete', 'put', 'trace', 'patch']) class View(object): From 92dbe3153a9e0c007ecd1987ccd5f3d796c901af Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 24 Jan 2012 18:00:14 -0500 Subject: [PATCH 007/142] Export .epub with docs, #388. --- Makefile | 3 ++- docs/_templates/sidebarintro.html | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 43f4727540..b7094ce657 100644 --- a/Makefile +++ b/Makefile @@ -23,12 +23,13 @@ clean-pyc: find . -name '*~' -exec rm -f {} + upload-docs: - $(MAKE) -C docs html dirhtml latex + $(MAKE) -C docs html dirhtml latex epub $(MAKE) -C docs/_build/latex all-pdf cd docs/_build/; mv html flask-docs; zip -r flask-docs.zip flask-docs; mv flask-docs html rsync -a docs/_build/dirhtml/ pocoo.org:/var/www/flask.pocoo.org/docs/ rsync -a docs/_build/latex/Flask.pdf pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.pdf rsync -a docs/_build/flask-docs.zip pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.zip + rsync -a docs/_build/epub/Flask.epub pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.epub docs: $(MAKE) -C docs html diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 850fe86a10..7677722582 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -9,6 +9,7 @@

Other Formats

Useful Links

From 36f3184396f72b106911f497ce43c3415cb4f551 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Thu, 26 Jan 2012 13:24:15 -0500 Subject: [PATCH 008/142] Automate .mobi generation of docs, #388. --- Makefile | 6 ++++++ docs/_templates/sidebarintro.html | 1 + 2 files changed, 7 insertions(+) diff --git a/Makefile b/Makefile index b7094ce657..6fa4869303 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ clean-pyc: find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + +# ebook-convert docs: http://manual.calibre-ebook.com/cli/ebook-convert.html upload-docs: $(MAKE) -C docs html dirhtml latex epub $(MAKE) -C docs/_build/latex all-pdf @@ -30,6 +31,11 @@ upload-docs: rsync -a docs/_build/latex/Flask.pdf pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.pdf rsync -a docs/_build/flask-docs.zip pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.zip rsync -a docs/_build/epub/Flask.epub pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.epub + @echo 'Building .mobi from .epub...' + @echo 'Command `ebook-covert` is provided by calibre package.' + @echo 'Requires X-forwarding for Qt features used in conversion (ssh -X).' + @echo 'Do not mind "Invalid value for ..." CSS errors if .mobi renders.' + ssh -X pocoo.org ebook-convert /var/www/flask.pocoo.org/docs/flask-docs.epub /var/www/flask.pocoo.org/docs/flask-docs.mobi --cover http://flask.pocoo.org/docs/_images/logo-full.png --authors 'Armin Ronacher' docs: $(MAKE) -C docs html diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 7677722582..26e3226152 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -10,6 +10,7 @@

Other Formats

Useful Links

From 5cb50a46eeb29dddacf21d0348050e02ff96c3b5 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 31 Jan 2012 05:46:18 -0500 Subject: [PATCH 009/142] Fix Message Flashing doc, from SwashBuckla #pocoo. Provide a full example as promised in the doc. --- docs/patterns/flashing.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/patterns/flashing.rst b/docs/patterns/flashing.rst index f3d80d32ae..5f3b02ebd1 100644 --- a/docs/patterns/flashing.rst +++ b/docs/patterns/flashing.rst @@ -16,7 +16,11 @@ Simple Flashing So here is a full example:: - from flask import flash, redirect, url_for, render_template + from flask import Flask, flash, redirect, render_template, \ + request, url_for + + app = Flask(__name__) + app.secret_key = 'some_secret' @app.route('/') def index(): @@ -27,13 +31,17 @@ So here is a full example:: error = None if request.method == 'POST': if request.form['username'] != 'admin' or \ - request.form['password'] != 'secret': + request.form['password'] != 'secret': error = 'Invalid credentials' else: flash('You were successfully logged in') return redirect(url_for('index')) return render_template('login.html', error=error) + if __name__ == "__main__": + app.run() + + And here the ``layout.html`` template which does the magic: .. sourcecode:: html+jinja From 4aebc267bc67c5d8a1687c0e5a7ecc949d6e7d20 Mon Sep 17 00:00:00 2001 From: FND Date: Tue, 31 Jan 2012 13:54:46 +0100 Subject: [PATCH 010/142] Hyphenate "class-based" makes it more readable --- CHANGES | 4 ++-- docs/api.rst | 2 +- docs/extensiondev.rst | 4 ++-- docs/views.rst | 4 ++-- flask/views.py | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index 970f183a4d..7188e57b8d 100644 --- a/CHANGES +++ b/CHANGES @@ -89,7 +89,7 @@ Released on September 29th 2011, codename Rakija variable as well as ``SERVER_NAME`` are now properly used by the test client as defaults. - Added :attr:`flask.views.View.decorators` to support simpler decorating of - pluggable (class based) views. + pluggable (class-based) views. - Fixed an issue where the test client if used with the "with" statement did not trigger the execution of the teardown handlers. - Added finer control over the session cookie parameters. @@ -177,7 +177,7 @@ Released on June 28th 2011, codename Grappa might occur during request processing (for instance database connection errors, timeouts from remote resources etc.). - Blueprints can provide blueprint specific error handlers. -- Implemented generic :ref:`views` (class based views). +- Implemented generic :ref:`views` (class-based views). Version 0.6.1 ------------- diff --git a/docs/api.rst b/docs/api.rst index d2b621992e..ec7e4f6381 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -476,7 +476,7 @@ Signals .. _blinker: http://pypi.python.org/pypi/blinker -Class Based Views +Class-Based Views ----------------- .. versionadded:: 0.7 diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index ee0d5e608e..074d06ab6d 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -148,7 +148,7 @@ 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 +we will use the class-based approach because it will provide users with a manager object that handles opening and closing database connections. The Extension Code @@ -203,7 +203,7 @@ So here's what these lines of code do: 5. Finally, we add a `get_db` function that simplifies access to the context's database. -So why did we decide on a class based approach here? Because using our +So why did we decide on a class-based approach here? Because using our extension looks something like this:: from flask import Flask diff --git a/docs/views.rst b/docs/views.rst index 9270921b50..02c6270474 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -23,7 +23,7 @@ database and renders into a template:: This is simple and flexible, but if you want to provide this view in a generic fashion that can be adapted to other models and templates as well -you might want more flexibility. This is where pluggable class based +you might want more flexibility. This is where pluggable class-based views come into place. As the first step to convert this into a class based view you would do this:: @@ -70,7 +70,7 @@ this by itself is not helpful, so let's refactor the code a bit:: return User.query.all() This of course is not that helpful for such a small example, but it's good -enough to explain the basic principle. When you have a class based view +enough to explain the basic principle. When you have a class-based view the question comes up what `self` points to. The way this works is that whenever the request is dispatched a new instance of the class is created and the :meth:`~flask.views.View.dispatch_request` method is called with diff --git a/flask/views.py b/flask/views.py index 2aaaf156ec..79d62992dd 100644 --- a/flask/views.py +++ b/flask/views.py @@ -3,7 +3,7 @@ flask.views ~~~~~~~~~~~ - This module provides class based views inspired by the ones in Django. + This module provides class-based views inspired by the ones in Django. :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. @@ -50,7 +50,7 @@ def dispatch_request(self): #: A for which methods this pluggable view can handle. methods = None - #: The canonical way to decorate class based views is to decorate the + #: The canonical way to decorate class-based views is to decorate the #: return value of as_view(). However since this moves parts of the #: logic from the class declaration to the place where it's hooked #: into the routing system. @@ -89,7 +89,7 @@ def view(*args, **kwargs): view = decorator(view) # we attach the view class to the view function for two reasons: - # first of all it allows us to easily figure out what class based + # first of all it allows us to easily figure out what class-based # view this thing came from, secondly it's also used for instantiating # the view class so you can actually replace it with something else # for testing purposes and debugging. @@ -120,7 +120,7 @@ def __new__(cls, name, bases, d): class MethodView(View): - """Like a regular class based view but that dispatches requests to + """Like a regular class-based view but that dispatches requests to particular methods. For instance if you implement a method called :meth:`get` it means you will response to ``'GET'`` requests and the :meth:`dispatch_request` implementation will automatically From d33f9990c80bd25fd36e19ea4d010fe0a1a56c42 Mon Sep 17 00:00:00 2001 From: Priit Laes Date: Wed, 1 Feb 2012 14:49:46 +0200 Subject: [PATCH 011/142] Document context processors' variable functions --- docs/templating.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/templating.rst b/docs/templating.rst index bd940b0e49..15433f2a4a 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -186,3 +186,22 @@ The context processor above makes a variable called `user` available in the template with the value of `g.user`. This example is not very interesting because `g` is available in templates anyways, but it gives an idea how this works. + +It is also possible to inject functions that can have any number of +arguments:: + + @app.context_processor + def price_formatter(): + def loader(amount, currency=u'€'): + return u'{0:.2f}{1}.format(amount, currency) + return dict(format_price=loader) + +The above construct registers a "variable" function called +`format_price` which can then be used in template:: + + {{ format_price(0.33) }} + +The difference from regular context processor' variables is that functions +are evaluated upon template rendering compared to variables whose values +are created during `app` startup . Therefore "variable" functions make it +possible to inject dynamic data into templates. From 8ef2ca99b991f66f0b61975e883325fcf5c13046 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Wed, 1 Feb 2012 18:03:29 -0500 Subject: [PATCH 012/142] Reword context processors for functions. --- docs/templating.rst | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/templating.rst b/docs/templating.rst index 15433f2a4a..19217528c3 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -147,6 +147,8 @@ autoescape %}`` block: Whenever you do this, please be very cautious about the variables you are using in this block. +.. _registering-filters: + Registering Filters ------------------- @@ -176,7 +178,7 @@ context processors exist in Flask. Context processors run before the template is rendered and have the ability to inject new values into the template context. A context processor is a function that returns a dictionary. The keys and values of this dictionary are then merged with -the template context:: +the template context, for all templates in the app:: @app.context_processor def inject_user(): @@ -187,21 +189,21 @@ the template with the value of `g.user`. This example is not very interesting because `g` is available in templates anyways, but it gives an idea how this works. -It is also possible to inject functions that can have any number of -arguments:: +Variables are not limited to values; a context processor can also make +functions available to templates (since Python allows passing around +functions):: @app.context_processor - def price_formatter(): - def loader(amount, currency=u'€'): + def utility_processor(): + def format_price(amount, currency=u'€'): return u'{0:.2f}{1}.format(amount, currency) - return dict(format_price=loader) + return dict(format_price=format_price) -The above construct registers a "variable" function called -`format_price` which can then be used in template:: +The context processor above makes the `format_price` function available to all +templates:: {{ format_price(0.33) }} -The difference from regular context processor' variables is that functions -are evaluated upon template rendering compared to variables whose values -are created during `app` startup . Therefore "variable" functions make it -possible to inject dynamic data into templates. +You could also build `format_price` as a template filter (see +:ref:`registering-filters`), but this demonstrates how to pass functions in a +context processor. From 5a1bef4429f289eac52f8ccf57a1a8409898d489 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Wed, 1 Feb 2012 18:03:32 -0500 Subject: [PATCH 013/142] Demonstrate in docs how to use registered filters. Requested by @plaes on #pocoo irc. --- docs/templating.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/templating.rst b/docs/templating.rst index 19217528c3..d4878cdbba 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -168,7 +168,13 @@ The two following examples work the same and both reverse an object:: app.jinja_env.filters['reverse'] = reverse_filter In case of the decorator the argument is optional if you want to use the -function name as name of the filter. +function name as name of the filter. Once registered, you can use the filter +in your templates in the same way as Jinja2's builtin filters, for example if +you have a Python list in context called `mylist`:: + + {% for x in mylist | reverse %} + {% endfor %} + Context Processors ------------------ From dfd3ef6d5460d666226d1831de48cdaff72c1bb6 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Fri, 3 Feb 2012 13:19:04 -0500 Subject: [PATCH 014/142] Add cookie size limit note to sessions section. Result of discussion with jujule_ on #pocoo irc. --- docs/quickstart.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 368bd96c09..1d524a0984 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -798,6 +798,13 @@ not using the template engine (as in this example). Just take that thing and copy/paste it into your code and you're done. +A note on cookie-based sessions: Flask will take the values you put into the +session object and serialize them into a cookie. If you are finding some +values do not persist across requests, cookies are indeed enabled, and you are +not getting a clear error message, check the size of the cookie in your page +responses compared to the size supported by web browsers. + + Message Flashing ---------------- From 69e7a0a2a0391ed1bdd102deffbd98137790f959 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Fri, 3 Feb 2012 18:11:14 -0500 Subject: [PATCH 015/142] Move debugger details into a new section, #343. --- docs/errorhandling.rst | 69 ++++++++++++++++++++++++++++++++++++++++-- docs/quickstart.rst | 39 ++---------------------- 2 files changed, 70 insertions(+), 38 deletions(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index debb9d75bf..97ff4df22b 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -1,7 +1,7 @@ .. _application-errors: -Handling Application Errors -=========================== +Logging Application Errors +========================== .. versionadded:: 0.3 @@ -235,3 +235,68 @@ iterating over them to attach handlers:: for logger in loggers: logger.addHandler(mail_handler) logger.addHandler(file_handler) + + +Debugging Application Errors +============================ + +For production applications, configure your application with logging and +notifications as described in :ref:`application-errors`. This section provides +pointers when debugging deployment configuration and digging deeper with a +full-featured Python debugger. + + +When in Doubt, Run Manually +--------------------------- + +Having problems getting your application configured for production? If you +have shell access to your host, verify that you can run your application +manually from the shell in the deployment environment. Be sure to run under +the same user account as the configured deployment to troubleshoot permission +issues. You can use Flask's builtin development server with `debug=True` on +your production host, which is helpful in catching configuration issues, but +**be sure to do this temporarily in a controlled environment.** Do not run in +production with `debug=True`. + + +.. _working-with-debuggers: + +Working with Debuggers +---------------------- + +To dig deeper, possibly to trace code execution, Flask provides a debugger out +of the box (see :ref:`debug-mode`). If you would like to use another Python +debugger, note that debuggers interfere with each other. You have to set some +options in order to use your favorite debugger: + +* ``debug`` - whether to enable debug mode and catch exceptinos +* ``use_debugger`` - whether to use the internal Flask debugger +* ``use_reloader`` - whether to reload and fork the process on exception + +``debug`` must be True (i.e., exceptions must be caught) in order for the other +two options to have any value. + +If you're using Aptana/Eclipse for debugging you'll need to set both +``use_debugger`` and ``use_reloader`` to False. + +A possible useful pattern for configuration is to set the following in your +config.yaml (change the block as approriate for your application, of course):: + + FLASK: + DEBUG: True + DEBUG_WITH_APTANA: True + +Then in your application's entry-point (main.py), you could have something like:: + + if __name__ == "__main__": + # To allow aptana to receive errors, set use_debugger=False + app = create_app(config="config.yaml") + + if app.debug: use_debugger = True + try: + # Disable Flask's debugger if external debugger is requested + use_debugger = not(app.config.get('DEBUG_WITH_APTANA')) + except: + pass + app.run(use_debugger=use_debugger, debug=app.debug, + use_reloader=use_debugger, host='0.0.0.0') diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 1d524a0984..9fde0c2fce 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -77,6 +77,8 @@ To stop the server, hit control-C. This tells your operating system to listen on all public IPs. +.. _debug-mode: + Debug Mode ---------- @@ -112,42 +114,7 @@ Screenshot of the debugger in action: :class: screenshot :alt: screenshot of debugger in action -.. admonition:: Working With Other Debuggers - - Debuggers interfere with each other. - That said, you may still wish to use the debugger in a tool of your choice. - Flask provides the following options to manage the debug process: - - * ``debug`` - whether to enable debug mode and catch exceptinos - * ``use_debugger`` - whether to use the internal Flask debugger - * ``use_reloader`` - whether to reload and fork the process on exception - - ``debug`` must be True (i.e., exceptions must be caught) in order for the - other two options to have any value. - - If you're using Aptana/Eclipse for debugging you'll need to set both - ``use_debugger`` and ``use_reloader`` to False. - - A possible useful pattern for configuration is to set the following in your - config.yaml (change the block as approriate for your application, of course):: - - FLASK: - DEBUG: True - DEBUG_WITH_APTANA: True - - Then in your application's entry-point (main.py), you could have something like:: - - if __name__ == "__main__": - # To allow aptana to receive errors, set use_debugger=False - app = create_app(config="config.yaml") - - if app.debug: use_debugger = True - try: - # Disable Flask's debugger if external debugger is requested - use_debugger = not(app.config.get('DEBUG_WITH_APTANA')) - except: - pass - app.run(use_debugger=use_debugger, debug=app.debug, use_reloader=use_debugger, host='0.0.0.0') +Have another debugger in mind? See :ref:`working-with-debuggers`. Routing From 84b96ac9d398afd5a620c48cfe9e969b95e6577a Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sat, 4 Feb 2012 10:37:58 -0500 Subject: [PATCH 016/142] Add pypy to tox.ini. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 15911b4ca4..d9db8990e0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py25,py26,py27 +envlist=py25,py26,py27,pypy [testenv] commands=make test From 96d7f207878bcd561c4085312eb9e235b9dc4d2d Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sat, 4 Feb 2012 10:44:16 -0500 Subject: [PATCH 017/142] Fix tox warning, test "not installed in testenv". --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d9db8990e0..91c6d6641d 100644 --- a/tox.ini +++ b/tox.ini @@ -2,4 +2,4 @@ envlist=py25,py26,py27,pypy [testenv] -commands=make test +commands=python run-tests.py From 3de8de1985f46297243eab340abd6c45c82bb9c4 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 6 Feb 2012 20:19:32 -0500 Subject: [PATCH 018/142] pip > easy_install --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d41a3ecabc..3a302cea0c 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ def hello(): :: - $ easy_install Flask + $ pip install Flask $ python hello.py * Running on http://localhost:5000/ From 4a75198f36625b472215a4ef6192c41f950fa202 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 6 Feb 2012 20:28:17 -0500 Subject: [PATCH 019/142] pip and distribute installation --- docs/installation.rst | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 46f4318603..0d3fcd7491 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -100,7 +100,7 @@ System-Wide Installation This is possible as well, though I do not recommend it. Just run `easy_install` with root privileges:: - $ sudo easy_install Flask + $ sudo pip install Flask (On Windows systems, run it in a command-prompt window with administrator privleges, and leave out `sudo`.) @@ -110,7 +110,7 @@ Living on the Edge ------------------ If you want to work with the latest version of Flask, there are two ways: you -can either let `easy_install` pull in the development version, or you can tell +can either let `pip` pull in the development version, or you can tell it to operate on a git checkout. Either way, virtualenv is recommended. Get the git checkout in a new virtualenv and run in development mode:: @@ -138,19 +138,19 @@ To just get the development version without git, do this instead:: $ . env/bin/activate New python executable in env/bin/python Installing setuptools............done. - $ easy_install Flask==dev + $ pip install Flask==dev ... Finished processing dependencies for Flask==dev .. _windows-easy-install: -`easy_install` on Windows -------------------------- +`pip` and `distribute` on Windows +----------------------------------- On Windows, installation of `easy_install` is a little bit trickier, but still -quite easy. The easiest way to do it is to download the `ez_setup.py`_ file -and run it. The easiest way to run the file is to open your downloads folder -and double-click on the file. +quite easy. The easiest way to do it is to download the +`distribute_setup.py`_ file and run it. The easiest way to run the file is to +open your downloads folder and double-click on the file. Next, add the `easy_install` command and other Python scripts to the command search path, by adding your Python installation's Scripts folder @@ -161,14 +161,18 @@ Then click on "Advanced System settings" (in Windows XP, click on the Finally, double-click on the "Path" variable in the "System variables" section, and add the path of your Python interpreter's Scripts folder. Be sure to delimit it from existing values with a semicolon. Assuming you are using -Python 2.6 on the default path, add the following value:: +Python 2.7 on the default path, add the following value:: - ;C:\Python26\Scripts + ;C:\Python27\Scripts And you are done! To check that it worked, open the Command Prompt and execute ``easy_install``. If you have User Account Control enabled on Windows Vista or Windows 7, it should prompt you for administrator privileges. +Now that you have ``easy_install``, you can use it to install ``pip``:: -.. _ez_setup.py: http://peak.telecommunity.com/dist/ez_setup.py + > easy_install pip + + +.. _distribute_setup.py: http://python-distribute.org/distribute_setup.py From 73a859533525febe42c0645ec25eeaa049123973 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 6 Feb 2012 20:41:13 -0500 Subject: [PATCH 020/142] vent and no more . --- docs/installation.rst | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 0d3fcd7491..075f12cb43 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -66,22 +66,18 @@ folder within:: $ mkdir myproject $ cd myproject - $ virtualenv env - New python executable in env/bin/python + $ virtualenv venv + New python executable in venv/bin/python Installing setuptools............done. Now, whenever you want to work on a project, you only have to activate the corresponding environment. On OS X and Linux, do the following:: - $ . env/bin/activate - -(Note the space between the dot and the script name. The dot means that this -script should run in the context of the current shell. If this command does -not work in your shell, try replacing the dot with ``source``.) + $ source venv/bin/activate If you are a Windows user, the following command is for you:: - $ env\scripts\activate + $ venv\scripts\activate Either way, you should now be using your virtualenv (notice how the prompt of your shell has changed to show the active environment). @@ -89,7 +85,7 @@ your shell has changed to show the active environment). Now you can just enter the following command to get Flask activated in your virtualenv:: - $ easy_install Flask + $ pip install Flask A few seconds later and you are good to go. From 60f7e3a7b463db68f3cc22085b6c5eaac985e7f7 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 6 Feb 2012 20:41:13 -0500 Subject: [PATCH 021/142] vent and no more . --- docs/installation.rst | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 0d3fcd7491..71c4b59c1e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -66,22 +66,18 @@ folder within:: $ mkdir myproject $ cd myproject - $ virtualenv env - New python executable in env/bin/python + $ virtualenv venv + New python executable in venv/bin/python Installing setuptools............done. Now, whenever you want to work on a project, you only have to activate the corresponding environment. On OS X and Linux, do the following:: - $ . env/bin/activate - -(Note the space between the dot and the script name. The dot means that this -script should run in the context of the current shell. If this command does -not work in your shell, try replacing the dot with ``source``.) + $ source venv/bin/activate If you are a Windows user, the following command is for you:: - $ env\scripts\activate + $ venv\scripts\activate Either way, you should now be using your virtualenv (notice how the prompt of your shell has changed to show the active environment). @@ -89,7 +85,7 @@ your shell has changed to show the active environment). Now you can just enter the following command to get Flask activated in your virtualenv:: - $ easy_install Flask + $ pip install Flask A few seconds later and you are good to go. @@ -118,10 +114,10 @@ Get the git checkout in a new virtualenv and run in development mode:: $ git clone http://github.com/mitsuhiko/flask.git Initialized empty Git repository in ~/dev/flask/.git/ $ cd flask - $ virtualenv env - $ . env/bin/activate + $ virtualenv venv --distribute New python executable in env/bin/python Installing setuptools............done. + $ source env/bin/activate $ python setup.py develop ... Finished processing dependencies for Flask @@ -134,10 +130,10 @@ To just get the development version without git, do this instead:: $ mkdir flask $ cd flask - $ virtualenv env - $ . env/bin/activate - New python executable in env/bin/python - Installing setuptools............done. + $ virtualenv venv --distribute + $ source venv/bin/activate + New python executable in venv/bin/python + Installing distribute............done. $ pip install Flask==dev ... Finished processing dependencies for Flask==dev From 5e848176e5b4db2958614e94024bd742830446eb Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 6 Feb 2012 21:05:51 -0500 Subject: [PATCH 022/142] typos and fixes --- docs/installation.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 71c4b59c1e..a5fe656281 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -68,7 +68,7 @@ folder within:: $ cd myproject $ virtualenv venv New python executable in venv/bin/python - Installing setuptools............done. + Installing distribute............done. Now, whenever you want to work on a project, you only have to activate the corresponding environment. On OS X and Linux, do the following:: @@ -99,7 +99,7 @@ This is possible as well, though I do not recommend it. Just run $ sudo pip install Flask (On Windows systems, run it in a command-prompt window with administrator -privleges, and leave out `sudo`.) +privileges, and leave out `sudo`.) Living on the Edge @@ -115,9 +115,9 @@ Get the git checkout in a new virtualenv and run in development mode:: Initialized empty Git repository in ~/dev/flask/.git/ $ cd flask $ virtualenv venv --distribute - New python executable in env/bin/python - Installing setuptools............done. - $ source env/bin/activate + New python executable in venv/bin/python + Installing distribute............done. + $ source venv/bin/activate $ python setup.py develop ... Finished processing dependencies for Flask From e070ede050fdb2ce155e13e29f5588c9831fa6b5 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 7 Feb 2012 10:42:25 -0500 Subject: [PATCH 023/142] Use "." not "source" for shell sourcing. Shell portability from mitsuhiko. --- docs/installation.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index a5fe656281..8e6a449765 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -73,7 +73,7 @@ folder within:: Now, whenever you want to work on a project, you only have to activate the corresponding environment. On OS X and Linux, do the following:: - $ source venv/bin/activate + $ . venv/bin/activate If you are a Windows user, the following command is for you:: @@ -117,7 +117,7 @@ Get the git checkout in a new virtualenv and run in development mode:: $ virtualenv venv --distribute New python executable in venv/bin/python Installing distribute............done. - $ source venv/bin/activate + $ . venv/bin/activate $ python setup.py develop ... Finished processing dependencies for Flask @@ -131,7 +131,7 @@ To just get the development version without git, do this instead:: $ mkdir flask $ cd flask $ virtualenv venv --distribute - $ source venv/bin/activate + $ . venv/bin/activate New python executable in venv/bin/python Installing distribute............done. $ pip install Flask==dev From 04bb720d3813c8cfe3ae46d2fb85dbc9a03a9b70 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 14 Feb 2012 18:13:29 -0500 Subject: [PATCH 024/142] Fix Blueprint example with template_folder, #403. --- docs/blueprints.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/blueprints.rst b/docs/blueprints.rst index 9422fd027c..4e3888c264 100644 --- a/docs/blueprints.rst +++ b/docs/blueprints.rst @@ -61,7 +61,8 @@ implement a blueprint that does simple rendering of static templates:: from flask import Blueprint, render_template, abort from jinja2 import TemplateNotFound - simple_page = Blueprint('simple_page', __name__) + simple_page = Blueprint('simple_page', __name__, + template_folder='templates') @simple_page.route('/', defaults={'page': 'index'}) @simple_page.route('/') From 0b3369355dadb39ac1ce9580d95004233031a287 Mon Sep 17 00:00:00 2001 From: Dmitry Shevchenko Date: Fri, 24 Feb 2012 00:46:20 -0600 Subject: [PATCH 025/142] Allow loading template from iterable --- flask/templating.py | 8 +++++--- flask/testsuite/templating.py | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/flask/templating.py b/flask/templating.py index 90e8772a3b..c809a63f0b 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -109,17 +109,19 @@ def _render(template, context, app): return rv -def render_template(template_name, **context): +def render_template(template_name_or_list, **context): """Renders a template from the template folder with the given context. - :param template_name: the name of the template to be rendered + :param template_name_or_list: the name of the template to be + rendered, or an iterable with template names + the first one existing will be rendered :param context: the variables that should be available in the context of the template. """ ctx = _request_ctx_stack.top ctx.app.update_template_context(context) - return _render(ctx.app.jinja_env.get_template(template_name), + return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list), context, ctx.app) diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index 759fe0f339..4a0ebdbccd 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -178,6 +178,25 @@ def index(): self.assert_equal(rv.data, 'Hello Custom World!') + def test_iterable_loader(self): + app = flask.Flask(__name__) + @app.context_processor + def context_processor(): + return {'whiskey': 'Jameson'} + @app.route('/') + def index(): + return flask.render_template( + ['no_template.xml', # should skip this one + 'simple_template.html', # should render this + 'context_template.html'], + value=23) + + rv = app.test_client().get('/') + self.assert_equal(rv.data, '

Jameson

') + + + + def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TemplatingTestCase)) From fdad6713eba3f7aaae6ca4f6f50bb8b72452c6d9 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Fri, 24 Feb 2012 09:33:11 -0500 Subject: [PATCH 026/142] Add updates to render_template to CHANGES, #409. --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 7188e57b8d..8311e2e758 100644 --- a/CHANGES +++ b/CHANGES @@ -42,6 +42,9 @@ Relase date to be decided, codename to be chosen. using configuration values, e.g. ``app.run(app.config.get('MYHOST'), app.config.get('MYPORT'))``, with proper behavior whether or not a config file is provided. +- 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. Version 0.8.1 From fc7fe628466857fd8b83c1b2b8501df2d15ae4a9 Mon Sep 17 00:00:00 2001 From: Andrew Ash Date: Wed, 22 Feb 2012 22:50:26 -0800 Subject: [PATCH 027/142] Update docs/errorhandling.rst --- docs/errorhandling.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 97ff4df22b..9e26196deb 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -280,7 +280,7 @@ If you're using Aptana/Eclipse for debugging you'll need to set both ``use_debugger`` and ``use_reloader`` to False. A possible useful pattern for configuration is to set the following in your -config.yaml (change the block as approriate for your application, of course):: +config.yaml (change the block as appropriate for your application, of course):: FLASK: DEBUG: True From 20a3281209a5e99271c911f9c1143b1e6f0fb0b5 Mon Sep 17 00:00:00 2001 From: awsum Date: Tue, 21 Feb 2012 22:04:36 +0200 Subject: [PATCH 028/142] Update docs/patterns/wtforms.rst --- docs/patterns/wtforms.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/patterns/wtforms.rst b/docs/patterns/wtforms.rst index ed530427c4..1bf466378d 100644 --- a/docs/patterns/wtforms.rst +++ b/docs/patterns/wtforms.rst @@ -85,8 +85,10 @@ Here's an example `_formhelpers.html` template with such a macro:
{{ field.label }}
{{ field(**kwargs)|safe }} {% if field.errors %} -
    - {% for error in field.errors %}
  • {{ error }}{% endfor %} +
      + {% for error in field.errors %} +
    • {{ error }}
    • + {% endfor %}
    {% endif %}
@@ -106,7 +108,7 @@ takes advantage of the `_formhelpers.html` template: .. sourcecode:: html+jinja {% from "_formhelpers.html" import render_field %} -
+
{{ render_field(form.username) }} {{ render_field(form.email) }} From b9f4e0bd9c6e200ae930c2d9c07c582be118b051 Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Sun, 26 Feb 2012 12:43:50 -0800 Subject: [PATCH 029/142] Remove redundant words from quickstart. --- docs/quickstart.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 9fde0c2fce..f23f957ded 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -168,8 +168,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 + Flask's URL rules are based on Werkzeug's routing module. The idea + behind that module is to ensure beautiful and unique URLs based on precedents laid down by Apache and earlier HTTP servers. Take these two rules:: @@ -234,7 +234,7 @@ some examples: (This also uses the :meth:`~flask.Flask.test_request_context` method, explained below. It tells Flask to behave as though it is handling a request, even -though were are interacting with it through a Python shell. Have a look at the +though we are interacting with it through a Python shell. Have a look at the explanation below. :ref:`context-locals`). Why would you want to build URLs instead of hard-coding them into your From 85ad4ffb605eee37cae9ffa1f82c5b6bb95c7820 Mon Sep 17 00:00:00 2001 From: Dmitry Shevchenko Date: Thu, 1 Mar 2012 02:07:26 -0600 Subject: [PATCH 030/142] Blueprint example app --- examples/blueprintexample/blueprintexample.py | 11 ++++++++ .../blueprintexample/simple_page/__init__.py | 0 .../simple_page/simple_page.py | 13 ++++++++++ .../simple_page/templates/pages/hello.html | 5 ++++ .../simple_page/templates/pages/index.html | 5 ++++ .../simple_page/templates/pages/layout.html | 25 +++++++++++++++++++ .../simple_page/templates/pages/world.html | 5 ++++ 7 files changed, 64 insertions(+) create mode 100644 examples/blueprintexample/blueprintexample.py create mode 100644 examples/blueprintexample/simple_page/__init__.py create mode 100644 examples/blueprintexample/simple_page/simple_page.py create mode 100644 examples/blueprintexample/simple_page/templates/pages/hello.html create mode 100644 examples/blueprintexample/simple_page/templates/pages/index.html create mode 100644 examples/blueprintexample/simple_page/templates/pages/layout.html create mode 100644 examples/blueprintexample/simple_page/templates/pages/world.html diff --git a/examples/blueprintexample/blueprintexample.py b/examples/blueprintexample/blueprintexample.py new file mode 100644 index 0000000000..bc0e41d455 --- /dev/null +++ b/examples/blueprintexample/blueprintexample.py @@ -0,0 +1,11 @@ +from flask import Flask +from simple_page.simple_page import simple_page + +app = Flask(__name__) +app.register_blueprint(simple_page) +# Blueprint can be registered many times +app.register_blueprint(simple_page, url_prefix='/pages') + + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/examples/blueprintexample/simple_page/__init__.py b/examples/blueprintexample/simple_page/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/blueprintexample/simple_page/simple_page.py b/examples/blueprintexample/simple_page/simple_page.py new file mode 100644 index 0000000000..cb82cc372c --- /dev/null +++ b/examples/blueprintexample/simple_page/simple_page.py @@ -0,0 +1,13 @@ +from flask import Blueprint, render_template, abort +from jinja2 import TemplateNotFound + +simple_page = Blueprint('simple_page', __name__, + template_folder='templates') + +@simple_page.route('/', defaults={'page': 'index'}) +@simple_page.route('/') +def show(page): + try: + return render_template('pages/%s.html' % page) + except TemplateNotFound: + abort(404) diff --git a/examples/blueprintexample/simple_page/templates/pages/hello.html b/examples/blueprintexample/simple_page/templates/pages/hello.html new file mode 100644 index 0000000000..7fca6668a9 --- /dev/null +++ b/examples/blueprintexample/simple_page/templates/pages/hello.html @@ -0,0 +1,5 @@ +{% extends "pages/layout.html" %} + +{% block body %} + Hello +{% endblock %} \ No newline at end of file diff --git a/examples/blueprintexample/simple_page/templates/pages/index.html b/examples/blueprintexample/simple_page/templates/pages/index.html new file mode 100644 index 0000000000..0ca3ffe2bd --- /dev/null +++ b/examples/blueprintexample/simple_page/templates/pages/index.html @@ -0,0 +1,5 @@ +{% extends "pages/layout.html" %} + +{% block body %} + Blueprint example page +{% endblock %} \ No newline at end of file diff --git a/examples/blueprintexample/simple_page/templates/pages/layout.html b/examples/blueprintexample/simple_page/templates/pages/layout.html new file mode 100644 index 0000000000..2efccb9553 --- /dev/null +++ b/examples/blueprintexample/simple_page/templates/pages/layout.html @@ -0,0 +1,25 @@ + +Simple Page Blueprint +
+

This is blueprint example

+

+ A simple page blueprint is registered under / and /pages
+ you can access it using this urls: +

+

+

+ Also you can register the same blueprint under another path +

+

+ + + + {% block body %} + {% endblock %} +
\ No newline at end of file diff --git a/examples/blueprintexample/simple_page/templates/pages/world.html b/examples/blueprintexample/simple_page/templates/pages/world.html new file mode 100644 index 0000000000..bdb5b16b01 --- /dev/null +++ b/examples/blueprintexample/simple_page/templates/pages/world.html @@ -0,0 +1,5 @@ +{% extends "pages/layout.html" %} + +{% block body %} + World +{% endblock %} \ No newline at end of file From 62621ccd133ffcbe2c88d18841c0669d03739ac0 Mon Sep 17 00:00:00 2001 From: Dmitry Shevchenko Date: Thu, 1 Mar 2012 02:24:56 -0600 Subject: [PATCH 031/142] Blueprint example tests --- .../blueprintexample/blueprintexample_test.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 examples/blueprintexample/blueprintexample_test.py diff --git a/examples/blueprintexample/blueprintexample_test.py b/examples/blueprintexample/blueprintexample_test.py new file mode 100644 index 0000000000..b8f93414fd --- /dev/null +++ b/examples/blueprintexample/blueprintexample_test.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +""" + Blueprint Example Tests + ~~~~~~~~~~~~~~ + + Tests the Blueprint example app +""" +import blueprintexample +import unittest + + +class BlueprintExampleTestCase(unittest.TestCase): + + def setUp(self): + self.app = blueprintexample.app.test_client() + + def test_urls(self): + r = self.app.get('/') + self.assertEquals(r.status_code, 200) + + r = self.app.get('/hello') + self.assertEquals(r.status_code, 200) + + r = self.app.get('/world') + self.assertEquals(r.status_code, 200) + + #second blueprint instance + r = self.app.get('/pages/hello') + self.assertEquals(r.status_code, 200) + + r = self.app.get('/pages/world') + self.assertEquals(r.status_code, 200) + + +if __name__ == '__main__': + unittest.main() From 76773e1d0a8cabbe048bb76f9306185e9d83a85c Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Thu, 1 Mar 2012 08:34:08 -0500 Subject: [PATCH 032/142] Fixed silent keyword arg to config.from_envvar. The ``silent`` keyword argument to Config.from_envvar was not being honored if the environment variable existed but the file that it mentioned did not. The fix was simple - pass the keyword argument on to the underlying call to ``from_pyfile``. I also noticed that the return value from ``from_pyfile`` was not being passed back so I fixed that as well. --- flask/config.py | 3 +-- flask/testsuite/config.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/flask/config.py b/flask/config.py index 67dbf9b7b3..759fd48822 100644 --- a/flask/config.py +++ b/flask/config.py @@ -106,8 +106,7 @@ def from_envvar(self, variable_name, silent=False): 'loaded. Set this variable and make it ' 'point to a configuration file' % variable_name) - self.from_pyfile(rv) - return True + return self.from_pyfile(rv, silent=silent) def from_pyfile(self, filename, silent=False): """Updates the values in the config from a Python file. This function diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index 00f77cea21..e10804c33a 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -69,6 +69,24 @@ def test_config_from_envvar(self): finally: os.environ = env + def test_config_from_envvar_missing(self): + env = os.environ + try: + os.environ = {'FOO_SETTINGS': 'missing.cfg'} + try: + app = flask.Flask(__name__) + app.config.from_envvar('FOO_SETTINGS') + except IOError, e: + msg = str(e) + self.assert_(msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):')) + self.assert_(msg.endswith("missing.cfg'")) + else: + self.assert_(0, 'expected config') + self.assert_(not app.config.from_envvar('FOO_SETTINGS', silent=True)) + finally: + os.environ = env + def test_config_missing(self): app = flask.Flask(__name__) try: From 8d7ca29a3554d20324ed9c75d9c095dfa8a8c439 Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Thu, 1 Mar 2012 08:53:58 -0500 Subject: [PATCH 033/142] Cleaned up test case for issue #414. --- flask/testsuite/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index e10804c33a..bf72925b2e 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -82,8 +82,8 @@ def test_config_from_envvar_missing(self): 'file (No such file or directory):')) self.assert_(msg.endswith("missing.cfg'")) else: - self.assert_(0, 'expected config') - self.assert_(not app.config.from_envvar('FOO_SETTINGS', silent=True)) + self.fail('expected IOError') + self.assertFalse(app.config.from_envvar('FOO_SETTINGS', silent=True)) finally: os.environ = env From 8445f0d939dc3c4a2e722dc6dd4938d02bc2e094 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Fri, 2 Mar 2012 07:46:39 -0300 Subject: [PATCH 034/142] Fixed assumption made on session implementations. In the snippet 'session.setdefault(...).append(...)', it was being assumed that changes made to mutable structures in the session are are always in sync with the session object, which is not true for session implementations that use a external storage for keeping their keys/values. --- flask/helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 25250d26c4..122c330f72 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -261,7 +261,9 @@ def flash(message, category='message'): messages and ``'warning'`` for warnings. However any kind of string can be used as category. """ - session.setdefault('_flashes', []).append((category, message)) + flashes = session.get('_flashes', []) + flashes.append((category, message)) + session['_flashes'] = flashes def get_flashed_messages(with_categories=False, category_filter=[]): From 7ed3cba6588fbd585b10e58e15e352e90874732d Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Thu, 8 Mar 2012 09:14:14 -0800 Subject: [PATCH 035/142] Split ebook build process into own make target. --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 6fa4869303..08811ac203 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,6 @@ clean-pyc: find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + -# ebook-convert docs: http://manual.calibre-ebook.com/cli/ebook-convert.html upload-docs: $(MAKE) -C docs html dirhtml latex epub $(MAKE) -C docs/_build/latex all-pdf @@ -31,7 +30,10 @@ upload-docs: rsync -a docs/_build/latex/Flask.pdf pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.pdf rsync -a docs/_build/flask-docs.zip pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.zip rsync -a docs/_build/epub/Flask.epub pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.epub - @echo 'Building .mobi from .epub...' + +# ebook-convert docs: http://manual.calibre-ebook.com/cli/ebook-convert.html +ebook: + @echo 'Using .epub from `make upload-docs` to create .mobi.' @echo 'Command `ebook-covert` is provided by calibre package.' @echo 'Requires X-forwarding for Qt features used in conversion (ssh -X).' @echo 'Do not mind "Invalid value for ..." CSS errors if .mobi renders.' From 9711fd402010b2e14a98879f0457174c3ca15a24 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein 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:: + + <VirtualHost *> + ServerName example.com + ScriptAlias / /path/to/yourapplication.fcgi/ + </VirtualHost> + +Another way is to use a custom WSGI middleware. For example on a shared web hosting:: + + .htaccess + + <IfModule mod_fcgid.c> + AddHandler fcgid-script .fcgi + <Files ~ (\.fcgi)> + SetHandler fcgid-script + Options +FollowSymLinks +ExecCGI + </Files> + </IfModule> + + <IfModule mod_rewrite.c> + Options +FollowSymlinks + RewriteEngine On + RewriteBase / + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ yourapplication.fcgi/$1 [QSA,L] + </IfModule> + + yourapplication.fcgi + + #!/usr/bin/python + #: optional path to your local python site-packages folder + import sys + sys.path.insert(0, '<your_local_path>/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 <http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModFastCGI>`_ (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 <ron.duplain@gmail.com> 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:: <VirtualHost *> ServerName example.com ScriptAlias / /path/to/yourapplication.fcgi/ </VirtualHost> -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:: <IfModule mod_fcgid.c> 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] </IfModule> - 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 <http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModFastCGI>`_ -(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 +<http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModFastCGI>`_ (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 <alekzvik@gmail.com> 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 <http://devcenter.heroku.com/articles/python>`_ -- `Deploying Flask on ep.io <https://www.ep.io/docs/quickstart/flask/>`_ - `Deploying WSGI on dotCloud <http://docs.dotcloud.com/services/python/>`_ with `Flask-specific notes <http://flask.pocoo.org/snippets/48/>`_ From d90f0afe39724040d0be92df054e2b1438886134 Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> 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 <verysimple@gmail.com> 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 <ron.duplain@gmail.com> 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 <ron.duplain@gmail.com> 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 <alekzvik@gmail.com> 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 <ron.duplain@gmail.com> 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 <url-building>`. + 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 <ron.duplain@gmail.com> 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 <ron.duplain@gmail.com> 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 <ron.duplain@gmail.com> 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 <ron.duplain@gmail.com> 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 <http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python/>`_. + +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 +<http://werkzeug.pocoo.org/docs/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 <ron.duplain@gmail.com> 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 <ron.duplain@gmail.com> 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 <ron.duplain@gmail.com> 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 <verysimple@gmail.com> 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('/<page>') + 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 <armin.ronacher@active-4.com> 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 <armin.ronacher@active-4.com> 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 <armin.ronacher@active-4.com> 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 <alekzvik@gmail.com> 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 <marc@marc-abramowitz.com> 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 <marc@marc-abramowitz.com> 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 <kuyanatan.nlao@gmail.com> 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 <brousch@gmail.com> 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 +<http://flask.pocoo.org/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 <santini@dsi.unimi.it> 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?= <web@ramiro.org> 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 <armin.ronacher@active-4.com> 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 <armin.ronacher@active-4.com> 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 <armin.ronacher@active-4.com> 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 <mdw1980@gmail.com> 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 <beverly.a.lau@gmail.com> 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 <armin.ronacher@active-4.com> 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 <armin.ronacher@active-4.com> 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 <armin.ronacher@active-4.com> 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 <armin.ronacher@active-4.com> 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 <armin.ronacher@active-4.com> 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 <armin.ronacher@active-4.com> 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 <metallourlante@gmail.com> 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.