Browse Source

Merge branch 'new-request-dispatching' into blueprints

pull/262/head
Armin Ronacher 14 years ago
parent
commit
673fa18e6d
  1. 10
      CHANGES
  2. 53
      docs/api.rst
  3. 18
      docs/config.rst
  4. 1
      docs/contents.rst.inc
  5. 2
      docs/deploying/others.rst
  6. 35
      docs/extensiondev.rst
  7. 4
      docs/patterns/fabric.rst
  8. 2
      docs/patterns/packages.rst
  9. 10
      docs/patterns/sqlalchemy.rst
  10. 32
      docs/patterns/sqlite3.rst
  11. 5
      docs/quickstart.rst
  12. 230
      docs/reqcontext.rst
  13. 65
      docs/shell.rst
  14. 16
      docs/signals.rst
  15. 31
      docs/tutorial/dbcon.rst
  16. 9
      docs/tutorial/setup.rst
  17. 47
      docs/upgrading.rst
  18. 5
      examples/flaskr/flaskr.py
  19. 5
      examples/minitwit/minitwit.py
  20. 4
      flask/__init__.py
  21. 139
      flask/app.py
  22. 4
      flask/config.py
  23. 35
      flask/ctx.py
  24. 41
      flask/helpers.py
  25. 3
      flask/logging.py
  26. 1
      flask/signals.py
  27. 8
      flask/wrappers.py
  28. 78
      tests/flask_tests.py

10
CHANGES

@ -38,11 +38,19 @@ Release date to be announced, codename to be selected
- Added `create_jinja_loader` to override the loader creation process.
- Implemented a silent flag for `config.from_pyfile`.
- Added `teardown_request` decorator, for functions that should run at the end
of a request regardless of whether an exception occurred.
of a request regardless of whether an exception occurred. Also the behavior
for `after_request` was changed. It's now no longer executed when an exception
is raised. See :ref:`upgrading-to-new-teardown-handling`
- Implemented :func:`flask.has_request_context`
- Deprecated `init_jinja_globals`. Override the
:meth:`~flask.Flask.create_jinja_environment` method instead to
achieve the same functionality.
- Added :func:`safe_join`
- The automatic JSON request data unpacking now looks at the charset
mimetype parameter.
- Don't modify the session on :func:`flask.get_flashed_messages` if there
are no messages in the session.
- `before_request` handlers are now able to abort requests with errors.
Version 0.6.1
-------------

53
docs/api.rst

@ -244,6 +244,8 @@ Useful Functions and Classes
.. autofunction:: send_from_directory
.. autofunction:: safe_join
.. autofunction:: escape
.. autoclass:: Markup
@ -308,6 +310,9 @@ Configuration
Useful Internals
----------------
.. autoclass:: flask.ctx.RequestContext
:members:
.. data:: _request_ctx_stack
The internal :class:`~werkzeug.local.LocalStack` that is used to implement
@ -345,23 +350,6 @@ Useful Internals
if ctx is not None:
return ctx.session
.. versionchanged:: 0.4
The request context is automatically popped at the end of the request
for you. In debug mode the request context is kept around if
exceptions happen so that interactive debuggers have a chance to
introspect the data. With 0.4 this can also be forced for requests
that did not fail and outside of `DEBUG` mode. By setting
``'flask._preserve_context'`` to `True` on the WSGI environment the
context will not pop itself at the end of the request. This is used by
the :meth:`~flask.Flask.test_client` for example to implement the
deferred cleanup functionality.
You might find this helpful for unittests where you need the
information from the context local around for a little longer. Make
sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
that situation, otherwise your unittests will leak memory.
Signals
-------
@ -399,6 +387,12 @@ Signals
in debug mode, where no exception handling happens. The exception
itself is passed to the subscriber as `exception`.
.. 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.
.. currentmodule:: None
.. class:: flask.signals.Namespace
@ -416,28 +410,3 @@ Signals
operations, including connecting.
.. _blinker: http://pypi.python.org/pypi/blinker
.. _notes-on-proxies:
Notes On Proxies
----------------
Some of the objects provided by Flask are proxies to other objects. The
reason behind this is that these proxies are shared between threads and
they have to dispatch to the actual object bound to a thread behind the
scenes as necessary.
Most of the time you don't have to care about that, but there are some
exceptions where it is good to know that this object is an actual proxy:
- The proxy objects do not fake their inherited types, so if you want to
perform actual instance checks, you have to do that on the instance
that is being proxied (see `_get_current_object` below).
- if the object reference is important (so for example for sending
:ref:`signals`)
If you need to get access to the underlying object that is proxied, you
can use the :meth:`~werkzeug.local.LocalProxy._get_current_object` method::
app = current_app._get_current_object()
my_signal.send(app)

18
docs/config.rst

@ -51,7 +51,7 @@ The following configuration values are used internally by Flask:
.. tabularcolumns:: |p{6.5cm}|p{8.5cm}|
=============================== =========================================
================================= =========================================
``DEBUG`` enable/disable debug mode
``TESTING`` enable/disable testing mode
``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the
@ -59,6 +59,15 @@ The following configuration values are used internally by Flask:
explicitly set to `None` this is
implicitly true if either `TESTING` or
`DEBUG` is true.
``PRESERVE_CONTEXT_ON_EXCEPTION`` By default if the application is in
debug mode the request context is not
popped on exceptions to enable debuggers
to introspect the data. This can be
disabled by this key. You can also use
this setting to force-enable it for non
debug execution which might be useful to
debug production applications (but also
very risky).
``SECRET_KEY`` the secret key
``SESSION_COOKIE_NAME`` the name of the session cookie
``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as
@ -71,7 +80,7 @@ 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.
=============================== =========================================
================================= =========================================
.. admonition:: More on ``SERVER_NAME``
@ -102,7 +111,7 @@ The following configuration values are used internally by Flask:
``MAX_CONTENT_LENGTH``
.. versionadded:: 0.7
``PROPAGATE_EXCEPTIONS``
``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION``
Configuring from Files
----------------------
@ -231,6 +240,7 @@ your configuration files. However here a list of good recommendations:
and exports the development configuration for you.
- Use a tool like `fabric`_ in production to push code and
configurations separately to the production server(s). For some
details about how to do that, head over to the :ref:`deploy` pattern.
details about how to do that, head over to the
:ref:`fabric-deployment` pattern.
.. _fabric: http://fabfile.org/

1
docs/contents.rst.inc

@ -17,6 +17,7 @@ instructions for web development with Flask.
errorhandling
config
signals
reqcontext
shell
patterns/index
deploying/index

2
docs/deploying/others.rst

@ -72,7 +72,7 @@ problematic values in the WSGI environment usually are `REMOTE_ADDR` and
but you might want to write your own WSGI middleware for specific setups.
The most common setup invokes the host being set from `X-Forwarded-Host`
and the remote address from `X-Forward-For`::
and the remote address from `X-Forwarded-For`::
from werkzeug.contrib.fixers import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)

35
docs/extensiondev.rst

@ -182,7 +182,7 @@ Here's the contents of the `flaskext/sqlite3.py` for copy/paste::
def __init__(self, app):
self.app = app
self.app.config.setdefault('SQLITE3_DATABASE', ':memory:')
self.app.after_request(self.after_request)
self.app.teardown_request(self.teardown_request)
self.app.before_request(self.before_request)
def connect(self):
@ -192,10 +192,9 @@ Here's the contents of the `flaskext/sqlite3.py` for copy/paste::
ctx = _request_ctx_stack.top
ctx.sqlite3_db = self.connect()
def after_request(self, response):
def teardown_request(self, exception):
ctx = _request_ctx_stack.top
ctx.sqlite3_db.close()
return response
def get_db(self):
ctx = _request_ctx_stack.top
@ -211,7 +210,7 @@ So here's what these lines of code do:
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
`after_request` handlers.
`teardown_request` handlers.
3. Next, we define a `connect` function 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
@ -264,7 +263,7 @@ Our extension could add an `init_app` function as follows::
def init_app(self, app):
self.app = app
self.app.config.setdefault('SQLITE3_DATABASE', ':memory:')
self.app.after_request(self.after_request)
self.app.teardown_request(self.teardown_request)
self.app.before_request(self.before_request)
def connect(self):
@ -274,10 +273,9 @@ Our extension could add an `init_app` function as follows::
ctx = _request_ctx_stack.top
ctx.sqlite3_db = self.connect()
def after_request(self, response):
def teardown_request(self, exception):
ctx = _request_ctx_stack.top
ctx.sqlite3_db.close()
return response
def get_db(self):
ctx = _request_ctx_stack.top
@ -292,6 +290,29 @@ and bind their app to the extension in another file::
manager.init_app(app)
End-Of-Request Behavior
-----------------------
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
wants to continue to support older versions of Flask. The following
pattern is a good way to support both::
def close_connection(response):
ctx = _request_ctx_stack.top
ctx.sqlite3_db.close()
return response
if hasattr(app, 'teardown_request'):
app.teardown_request(close_connection)
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
in between does not touch the passed parameter.
Learn from Others
-----------------

4
docs/patterns/fabric.rst

@ -32,7 +32,7 @@ hosts. These hosts can be defined either in the fabfile or on the command
line. In this case we will add them to the fabfile.
This is a basic first example that has the ability to upload the current
sourcecode to the server and install it into a already existing
sourcecode to the server and install it into a pre-existing
virtual environment::
from fabric.api import *
@ -53,7 +53,7 @@ virtual environment::
put('dist/%s.tar.gz' % dist, '/tmp/yourapplication.tar.gz')
# create a place where we can unzip the tarball, then enter
# that directory and unzip it
run('mkdir yourapplication')
run('mkdir /tmp/yourapplication')
with cd('/tmp/yourapplication'):
run('tar xzf /tmp/yourapplication.tar.gz')
# now setup the package with our virtual environment's

2
docs/patterns/packages.rst

@ -57,7 +57,7 @@ following quick checklist:
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.
Not the object itself, but the module it is in. Import the view module
*after the application object is created*.
**after the application object is created**.
Here's an example `__init__.py`::

10
docs/patterns/sqlalchemy.rst

@ -65,10 +65,9 @@ automatically remove database sessions at the end of the request for you::
from yourapplication.database import db_session
@app.after_request
def shutdown_session(response):
@app.teardown_request
def shutdown_session(exception=None):
db_session.remove()
return response
Here is an example model (put this into `models.py`, e.g.)::
@ -140,10 +139,9 @@ each request. Put this into your application module::
from yourapplication.database import db_session
@app.after_request
def shutdown_session(response):
@app.teardown_request
def shutdown_session(exception=None):
db_session.remove()
return response
Here is an example table and model (put this into `models.py`)::

32
docs/patterns/sqlite3.rst

@ -5,7 +5,7 @@ Using SQLite 3 with Flask
In Flask you can implement the opening of database connections at the
beginning of the request and closing at the end with the
:meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.after_request`
:meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.teardown_request`
decorators in combination with the special :class:`~flask.g` object.
So here is a simple example of how you can use SQLite 3 with Flask::
@ -22,10 +22,34 @@ So here is a simple example of how you can use SQLite 3 with Flask::
def before_request():
g.db = connect_db()
@app.after_request
def after_request(response):
@app.teardown_request
def teardown_request(exception):
g.db.close()
return response
Connect on Demand
-----------------
The downside of this approach is that this will only work if Flask
executed the before-request handlers for you. If you are attempting to
use the database from a script or the interactive Python shell you would
have to do something like this::
with app.test_request_context()
app.preprocess_request()
# now you can use the g.db object
In order to trigger the execution of the connection code. You won't be
able to drop the dependency on the request context this way, but you could
make it so that the application connects when necessary::
def get_connection():
db = getattr(g, '_db', None)
if db is None:
db = g._db = connect_db()
return db
Downside here is that you have to use ``db = get_connection()`` instead of
just being able to use ``g.db`` directly.
.. _easy-querying:

5
docs/quickstart.rst

@ -113,6 +113,11 @@ Screenshot of the debugger in action:
:class: screenshot
:alt: screenshot of debugger in action
.. admonition:: Working With Other Debuggers
Debuggers interfere with each other. If you are using another debugger
(e.g. PyDev or IntelliJ), you may need to set ``app.debug = False``.
Routing
-------

230
docs/reqcontext.rst

@ -0,0 +1,230 @@
.. _request-context:
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”.
Diving into Context Locals
--------------------------
Say you have a utility function that returns the URL the user should be
redirected to. Imagine it would always redirect to the URL's ``next``
parameter or the HTTP referrer or the index page::
from flask import request, url_for
def redirect_url():
return request.args.get('next') or \
request.referrer or \
url_for('index')
As you can see, it accesses the request object. If you try to run this
from a plain Python shell, this is the exception you will see:
>>> redirect_url()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'request'
That makes a lot of sense because we currently do not have a request we
could access. So we have to make a request and bind it to the current
context. The :attr:`~flask.Flask.test_request_context` method can create
us a :class:`~flask.ctx.RequestContext`:
>>> ctx = app.test_request_context('/?next=http://example.com/')
This context can be used in two ways. Either with the `with` statement
or by calling the :meth:`~flask.ctx.RequestContext.push` and
:meth:`~flask.ctx.RequestContext.pop` methods:
>>> ctx.push()
From that point onwards you can work with the request object:
>>> redirect_url()
u'http://example.com/'
Until you call `pop`:
>>> ctx.pop()
Because the request context is internally maintained as a stack you can
push and pop multiple times. This is very handy to implement things like
internal redirects.
For more information of how to utilize the request context from the
interactive Python shell, head over to the :ref:`shell` chapter.
How the Context Works
---------------------
If you look into how the Flask WSGI application internally works, you will
find a piece of code that looks very much like this::
def wsgi_app(self, environ):
with self.request_context(environ):
try:
response = self.full_dispatch_request()
except Exception, e:
response = self.make_response(self.handle_exception(e))
return response(environ, start_response)
The method :meth:`~Flask.request_context` returns a new
:class:`~flask.ctx.RequestContext` object and uses it in combination with
the `with` statement to bind the context. Everything that is called from
the same thread from this point onwards until the end of the `with`
statement will have access to the request globals (:data:`flask.request`
and others).
The request context internally works like a stack: The topmost level on
the stack is the current active request.
:meth:`~flask.ctx.RequestContext.push` adds the context to the stack on
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.
.. _callbacks-and-errors:
Callbacks and Errors
--------------------
What happens if an error occurs in Flask during request processing? This
particular behavior changed in 0.7 because we wanted to make it easier to
understand what is actually happening. The new behavior is quite simple:
1. Before each request, :meth:`~flask.Flask.before_request` functions are
executed. If one of these functions return a response, the other
functions are no longer called. In any case however the return value
is treated as a replacement for the view's return value.
2. If the :meth:`~flask.Flask.before_request` functions did not return a
response, the regular request handling kicks in and the view function
that was matched has the chance to return a response.
3. The return value of the view is then converted into an actual response
object and handed over to the :meth:`~flask.Flask.after_request`
functions which have the chance to replace it or modify it in place.
4. At the end of the request the :meth:`~flask.Flask.teardown_request`
functions are executed. This always happens, even in case of an
unhandled exception down the road.
Now what happens on errors? In production mode if an exception is not
caught, the 500 internal server handler is called. In development mode
however the exception is not further processed and bubbles up to the WSGI
server. That way things like the interactive debugger can provide helpful
debug information.
An important change in 0.7 is that the internal server error is now no
longer post processed by the after request callbacks and after request
callbacks are no longer guaranteed to be executed. This way the internal
dispatching code looks cleaner and is easier to customize and understand.
The new teardown functions are supposed to be used as a replacement for
things that absolutely need to happen at the end of request.
Teardown Callbacks
------------------
The teardown callbacks are special callbacks in that they are executed at
at different point. Strictly speaking they are independent of the actual
request handling as they are bound to the lifecycle of the
:class:`~flask.ctx.RequestContext` object. When the request context is
popped, the :meth:`~flask.Flask.teardown_request` functions are called.
This is important to know if the life of the request context is prolonged
by using the test client in a with statement of when using the request
context from the command line::
with app.test_client() as client:
resp = client.get('/foo')
# the teardown functions are still not called at that point
# even though the response ended and you have the response
# object in your hand
# only when the code reaches this point the teardown functions
# are called. Alternatively the same thing happens if another
# request was triggered from the test client
It's easy to see the behavior from the command line:
>>> app = Flask(__name__)
>>> @app.teardown_request
... def after_request(exception=None):
... print 'after request'
...
>>> ctx = app.test_request_context()
>>> ctx.push()
>>> ctx.pop()
after request
.. _notes-on-proxies:
Notes On Proxies
----------------
Some of the objects provided by Flask are proxies to other objects. The
reason behind this is that these proxies are shared between threads and
they have to dispatch to the actual object bound to a thread behind the
scenes as necessary.
Most of the time you don't have to care about that, but there are some
exceptions where it is good to know that this object is an actual proxy:
- The proxy objects do not fake their inherited types, so if you want to
perform actual instance checks, you have to do that on the instance
that is being proxied (see `_get_current_object` below).
- if the object reference is important (so for example for sending
:ref:`signals`)
If you need to get access to the underlying object that is proxied, you
can use the :meth:`~werkzeug.local.LocalProxy._get_current_object` method::
app = current_app._get_current_object()
my_signal.send(app)
Context Preservation on Error
-----------------------------
If an error occurs or not, at the end of the request the request context
is popped and all data associated with it is destroyed. During
development however that can be problematic as you might want to have the
information around for a longer time in case an exception occurred. In
Flask 0.6 and earlier in debug mode, if an exception occurred, the
request context was not popped so that the interactive debugger can still
provide you with important information.
Starting with Flask 0.7 you have finer control over that behavior by
setting the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. By
default it's linked to the setting of ``DEBUG``. If the application is in
debug mode the context is preserved, in production mode it's not.
Do not force activate ``PRESERVE_CONTEXT_ON_EXCEPTION`` in production mode
as it will cause your application to leak memory on exceptions. However
it can be useful during development to get the same error preserving
behavior as in development mode when attempting to debug an error that
only occurs under production settings.

65
docs/shell.rst

@ -1,3 +1,5 @@
.. _shell:
Working with the Shell
======================
@ -21,61 +23,37 @@ that these functions are not only there for interactive shell usage, but
also for unittesting and other situations that require a faked request
context.
Diving into Context Locals
--------------------------
Say you have a utility function that returns the URL the user should be
redirected to. Imagine it would always redirect to the URL's ``next``
parameter or the HTTP referrer or the index page::
from flask import request, url_for
def redirect_url():
return request.args.get('next') or \
request.referrer or \
url_for('index')
As you can see, it accesses the request object. If you try to run this
from a plain Python shell, this is the exception you will see:
Generally it's recommended that you read the :ref:`request-context`
chapter of the documentation first.
>>> redirect_url()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'request'
Creating a Request Context
--------------------------
That makes a lot of sense because we currently do not have a request we
could access. So we have to make a request and bind it to the current
context. The :attr:`~flask.Flask.test_request_context` method can create
us a request context:
The easiest way to create a proper request context from the shell is by
using the :attr:`~flask.Flask.test_request_context` method which creates
us a :class:`~flask.ctx.RequestContext`:
>>> ctx = app.test_request_context('/?next=http://example.com/')
>>> ctx = app.test_request_context()
This context can be used in two ways. Either with the `with` statement
(which unfortunately is not very handy for shell sessions). The
alternative way is to call the `push` and `pop` methods:
Normally you would use the `with` statement to make this request object
active, but in the shell it's easier to use the
:meth:`~flask.ctx.RequestContext.push` and
:meth:`~flask.ctx.RequestContext.pop` methods by hand:
>>> ctx.push()
From that point onwards you can work with the request object:
>>> redirect_url()
u'http://example.com/'
Until you call `pop`:
From that point onwards you can work with the request object until you
call `pop`:
>>> ctx.pop()
>>> redirect_url()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'request'
Firing Before/After Request
---------------------------
By just creating a request context, you still don't have run the code that
is normally run before a request. This probably results in your database
being unavailable, the current user not being stored on the
is normally run before a request. This might result in your database
being unavailable if you are connecting to the database in a
before-request callback or the current user not being stored on the
:data:`~flask.g` object etc.
This however can easily be done yourself. Just call
@ -96,6 +74,11 @@ a response object:
<Response 0 bytes [200 OK]>
>>> ctx.pop()
The functions registered as :meth:`~flask.Flask.teardown_request` are
automatically called when the context is popped. So this is the perfect
place to automatically tear down resources that were needed by the request
context (such as database connections).
Further Improving the Shell Experience
--------------------------------------

16
docs/signals.rst

@ -236,4 +236,20 @@ The following signals exist in Flask:
from flask import got_request_exception
got_request_exception.connect(log_exception, app)
.. data:: flask.request_tearing_down
:noindex:
This signal is sent when the request 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):
session.close()
from flask import request_tearing_down
request_tearing_down.connect(close_db_connection, app)
.. _blinker: http://pypi.python.org/pypi/blinker

31
docs/tutorial/dbcon.rst

@ -9,22 +9,8 @@ connection in all our functions so it makes sense to initialize them
before each request and shut them down afterwards.
Flask allows us to do that with the :meth:`~flask.Flask.before_request`,
:meth:`~flask.Flask.after_request` and :meth:`~flask.Flask.teardown_request`
decorators. In debug mode, if an error is raised,
:meth:`~flask.Flask.after_request` won't be run, and you'll have access to the
db connection in the interactive debugger::
@app.before_request
def before_request():
g.db = connect_db()
@app.after_request
def after_request(response):
g.db.close()
return response
If you want to guarantee that the connection is always closed in debug mode, you
can close it in a function decorated with :meth:`~flask.Flask.teardown_request`:
:meth:`~flask.Flask.teardown_request` and :meth:`~flask.Flask.teardown_request`
decorators::
@app.before_request
def before_request():
@ -36,18 +22,17 @@ can close it in a function decorated with :meth:`~flask.Flask.teardown_request`:
Functions marked with :meth:`~flask.Flask.before_request` are called before
a request and passed no arguments. Functions marked with
:meth:`~flask.Flask.after_request` are called after a request and
:meth:`~flask.Flask.teardown_request` are called after a request and
passed the response that will be sent to the client. They have to return
that response object or a different one. In this case we just return it
unchanged.
Functions marked with :meth:`~flask.Flask.teardown_request` get called after the
that response object or a different one. They are however not guaranteed
to be executed if an exception is raised, this is where functions marked with
:meth:`~flask.Flask.teardown_request` come in. They get called after the
response has been constructed. They are not allowed to modify the request, and
their return values are ignored. If an exception occurred while the request was
being processed, it is passed to each function; otherwise, None is passed in.
being processed, it is passed to each function; otherwise, `None` is passed in.
We store our current database connection on the special :data:`~flask.g`
object that flask provides for us. This object stores information for one
object that Flask provides for us. This object stores information for one
request only and is available from within each function. Never store such
things on other objects because this would not work with threaded
environments. That special :data:`~flask.g` object does some magic behind

9
docs/tutorial/setup.rst

@ -70,7 +70,14 @@ server if we want to run that file as a standalone application::
app.run()
With that out of the way you should be able to start up the application
without problems. When you head over to the server you will get an 404
without problems. Do this with the following command::
python flaskr.py
You will see a message telling you that server has started along with
the address at which you can access it.
When you head over to the server in your browser you will get an 404
page not found error because we don't have any views yet. But we will
focus on that a little later. First we should get the database working.

47
docs/upgrading.rst

@ -22,6 +22,11 @@ installation, make sure to pass it the ``-U`` parameter::
Version 0.7
-----------
The following backwards incompatible changes exist from 0.6 to 0.7
Bug in Request Locals
`````````````````````
Due to a bug in earlier implementations the request local proxies now
raise a :exc:`RuntimeError` instead of an :exc:`AttributeError` when they
are unbound. If you caught these exceptions with :exc:`AttributeError`
@ -44,6 +49,48 @@ New code::
return send_file(my_file_object, add_etags=False)
.. _upgrading-to-new-teardown-handling:
Upgrading to new Teardown Handling
``````````````````````````````````
We streamlined the behavior of the callbacks for request handling. For
things that modify the response the :meth:`~flask.Flask.after_request`
decorators continue to work as expected, but for things that absolutely
must happen at the end of request we introduced the new
:meth:`~flask.Flask.teardown_request` decorator. Unfortunately that
change also made after-request work differently under error conditions.
It's not consistently skipped if exceptions happen whereas previously it
might have been called twice to ensure it is executed at the end of the
request.
If you have database connection code that looks like this::
@app.after_request
def after_request(response):
g.db.close()
return response
You are now encouraged to use this instead::
@app.teardown_request
def after_request(exception):
g.db.close()
On the upside this change greatly improves the internal code flow and
makes it easier to customize the dispatching and error handling. This
makes it now a lot easier to write unit tests as you can prevent closing
down of database connections for a while. You can take advantage of the
fact that the teardown callbacks are called when the response context is
removed from the stack so a test can query the database after request
handling::
with app.test_client() as client:
resp = client.get('/')
# g.db is still bound if there is such a thing
# and here it's gone
Version 0.6
-----------

5
examples/flaskr/flaskr.py

@ -47,11 +47,10 @@ def before_request():
g.db = connect_db()
@app.after_request
def after_request(response):
@app.teardown_request
def teardown_request(exception):
"""Closes the database again at the end of the request."""
g.db.close()
return response
@app.route('/')

5
examples/minitwit/minitwit.py

@ -82,11 +82,10 @@ def before_request():
[session['user_id']], one=True)
@app.after_request
def after_request(response):
@app.teardown_request
def teardown_request(exception):
"""Closes the database again at the end of the request."""
g.db.close()
return response
@app.route('/')

4
flask/__init__.py

@ -19,7 +19,7 @@ from .app import Flask, Request, Response
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
get_template_attribute, make_response, safe_join
from .globals import current_app, g, request, session, _request_ctx_stack
from .ctx import has_request_context
from .module import Module
@ -28,7 +28,7 @@ from .session import Session
# the signals
from .signals import signals_available, template_rendered, request_started, \
request_finished, got_request_exception
request_finished, got_request_exception, request_tearing_down
# only import json if it's available
if json_available:

139
flask/app.py

@ -25,13 +25,14 @@ from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
locked_cached_property, _tojson_filter, _endpoint_from_view_func
from .wrappers import Request, Response
from .config import ConfigAttribute, Config
from .ctx import _RequestContext
from .ctx import RequestContext
from .globals import _request_ctx_stack, request
from .session import Session, _NullSession
from .module import _ModuleSetupState
from .templating import DispatchingJinjaLoader, Environment, \
_default_template_ctx_processor
from .signals import request_started, request_finished, got_request_exception
from .signals import request_started, request_finished, got_request_exception, \
request_tearing_down
# a lock used for logger initialization
_logger_lock = Lock()
@ -124,6 +125,9 @@ class Flask(_PackageBoundObject):
#: For example this might activate unittest helpers that have an
#: additional runtime cost which should not be enabled by default.
#:
#: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the
#: default it's implicitly enabled.
#:
#: This attribute can also be configured from the config with the
#: `TESTING` configuration key. Defaults to `False`.
testing = ConfigAttribute('TESTING')
@ -194,6 +198,7 @@ class Flask(_PackageBoundObject):
'DEBUG': False,
'TESTING': False,
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': None,
'SESSION_COOKIE_NAME': 'session',
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
@ -339,6 +344,19 @@ class Flask(_PackageBoundObject):
return rv
return self.testing or self.debug
@property
def preserve_context_on_exception(self):
"""Returns the value of the `PRESERVE_CONTEXT_ON_EXCEPTION`
configuration value in case it's set, otherwise a sensible default
is returned.
.. versionadded:: 0.7
"""
rv = self.config['PRESERVE_CONTEXT_ON_EXCEPTION']
if rv is not None:
return rv
return self.debug
@property
def logger(self):
"""A :class:`logging.Logger` object for this application. The
@ -771,13 +789,38 @@ class Flask(_PackageBoundObject):
return f
def after_request(self, f):
"""Register a function to be run after each request."""
"""Register a function to be run after each request. Your function
must take one parameter, a :attr:`response_class` object and return
a new response object or the same (see :meth:`process_response`).
As of Flask 0.7 this function might not be executed at the end of the
request in case an unhandled exception ocurred.
"""
self.after_request_funcs.setdefault(None, []).append(f)
return f
def teardown_request(self, f):
"""Register a function to be run at the end of each request,
regardless of whether there was an exception or not.
regardless of whether there was an exception or not. These functions
are executed when the request context is popped, even if not an
actual request was performed.
Example::
ctx = app.test_request_context()
ctx.push()
...
ctx.pop()
When ``ctx.pop()`` is executed in the above example, the teardown
functions are called just before the request context moves from the
stack of active contexts. This becomes relevant if you are using
such constructs in tests.
Generally teardown functions must take every necesary step to avoid
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.
"""
self.teardown_request_funcs.setdefault(None, []).append(f)
return f
@ -808,10 +851,21 @@ class Flask(_PackageBoundObject):
.. versionadded: 0.3
"""
exc_type, exc_value, tb = sys.exc_info()
got_request_exception.send(self, exception=e)
handler = self.error_handlers.get(500)
if self.propagate_exceptions:
raise
# if we want to repropagate the exception, we can attempt to
# raise it with the whole traceback in case we can do that
# (the function was actually called from the except part)
# otherwise, we just raise the error again
if exc_value is e:
raise exc_type, exc_value, tb
else:
raise e
self.logger.exception('Exception on %s [%s]' % (
request.path,
request.method
@ -825,9 +879,12 @@ class Flask(_PackageBoundObject):
return value of the view or error handler. This does not have to
be a response object. In order to convert the return value to a
proper response object, call :func:`make_response`.
.. versionchanged:: 0.7
This no longer does the exception handling, this code was
moved to the new :meth:`full_dispatch_request`.
"""
req = _request_ctx_stack.top.request
try:
if req.routing_exception is not None:
raise req.routing_exception
rule = req.url_rule
@ -838,8 +895,25 @@ class Flask(_PackageBoundObject):
return self.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
return self.view_functions[rule.endpoint](**req.view_args)
def full_dispatch_request(self):
"""Dispatches the request and on top of that performs request
pre and postprocessing as well as HTTP exception catching and
error handling.
.. versionadded:: 0.7
"""
try:
request_started.send(self)
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
except HTTPException, e:
return self.handle_http_exception(e)
rv = self.handle_http_exception(e)
response = self.make_response(rv)
response = self.process_response(response)
request_finished.send(self, response=response)
return response
def make_default_options_response(self):
"""This method is called to create the default `OPTIONS` response.
@ -949,7 +1023,10 @@ class Flask(_PackageBoundObject):
def do_teardown_request(self):
"""Called after the actual request dispatching and will
call every as :meth:`teardown_request` decorated function.
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.
"""
funcs = reversed(self.teardown_request_funcs.get(None, ()))
mod = request.module
@ -960,12 +1037,13 @@ class Flask(_PackageBoundObject):
rv = func(exc)
if rv is not None:
return rv
request_tearing_down.send(self)
def request_context(self, environ):
"""Creates a request context from the given environment and binds
it to the current context. This must be used in combination with
the `with` statement because the request is only bound to the
current context for the duration of the `with` block.
"""Creates a :class:`~flask.ctx.RequestContext` from the given
environment and binds it to the current context. This must be used in
combination with the `with` statement because the request is only bound
to the current context for the duration of the `with` block.
Example usage::
@ -983,22 +1061,13 @@ class Flask(_PackageBoundObject):
finally:
ctx.pop()
The big advantage of this approach is that you can use it without
the try/finally statement in a shell for interactive testing:
>>> ctx = app.test_request_context()
>>> ctx.bind()
>>> request.path
u'/'
>>> ctx.unbind()
.. versionchanged:: 0.3
Added support for non-with statement usage and `with` statement
is now passed the ctx object.
:param environ: a WSGI environment
"""
return _RequestContext(self, environ)
return RequestContext(self, environ)
def test_request_context(self, *args, **kwargs):
"""Creates a WSGI environment from the given values (see
@ -1033,16 +1102,11 @@ class Flask(_PackageBoundObject):
Then you still have the original application object around and
can continue to call methods on it.
.. versionchanged:: 0.4
The :meth:`after_request` functions are now called even if an
error handler took over request processing. This ensures that
even if an exception happens database have the chance to
properly close the connection.
.. versionchanged:: 0.7
The :meth:`teardown_request` functions get called at the very end of
processing the request. If an exception was thrown, it gets passed to
each teardown_request function.
The behavior of the before and after request callbacks was changed
under error conditions and a new callback was added that will
always execute at the end of the request, independent on if an
error ocurred or not. See :ref:`callbacks-and-errors`.
:param environ: a WSGI environment
:param start_response: a callable accepting a status code,
@ -1051,20 +1115,9 @@ class Flask(_PackageBoundObject):
"""
with self.request_context(environ):
try:
request_started.send(self)
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
response = self.make_response(rv)
response = self.full_dispatch_request()
except Exception, e:
response = self.make_response(self.handle_exception(e))
try:
response = self.process_response(response)
except Exception, e:
response = self.make_response(self.handle_exception(e))
finally:
self.do_teardown_request()
request_finished.send(self, response=response)
return response(environ, start_response)
def __call__(self, environ, start_response):

4
flask/config.py

@ -141,8 +141,8 @@ class Config(dict):
Objects are usually either modules or classes.
Just the uppercase variables in that object are stored in the config
after lowercasing. Example usage::
Just the uppercase variables in that object are stored in the config.
Example usage::
app.config.from_object('yourapplication.default_config')
from yourapplication import default_config

35
flask/ctx.py

@ -51,11 +51,34 @@ def has_request_context():
return _request_ctx_stack.top is not None
class _RequestContext(object):
class RequestContext(object):
"""The request context contains all request relevant information. It is
created at the beginning of the request and pushed to the
`_request_ctx_stack` and removed at the end of it. It will create the
URL adapter and request object for the WSGI environment provided.
Do not attempt to use this class directly, instead use
:meth:`~flask.Flask.test_request_context` and
:meth:`~flask.Flask.request_context` to create this object.
When the request context is popped, it will evaluate all the
functions registered on the application for teardown execution
(:meth:`~flask.Flask.teardown_request`).
The request context is automatically popped at the end of the request
for you. In debug mode the request context is kept around if
exceptions happen so that interactive debuggers have a chance to
introspect the data. With 0.4 this can also be forced for requests
that did not fail and outside of `DEBUG` mode. By setting
``'flask._preserve_context'`` to `True` on the WSGI environment the
context will not pop itself at the end of the request. This is used by
the :meth:`~flask.Flask.test_client` for example to implement the
deferred cleanup functionality.
You might find this helpful for unittests where you need the
information from the context local around for a little longer. Make
sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
that situation, otherwise your unittests will leak memory.
"""
def __init__(self, app, environ):
@ -74,7 +97,7 @@ class _RequestContext(object):
self.request.routing_exception = e
def push(self):
"""Binds the request context."""
"""Binds the request context to the current context."""
_request_ctx_stack.push(self)
# Open the session at the moment that the request context is
@ -85,7 +108,11 @@ class _RequestContext(object):
self.session = _NullSession()
def pop(self):
"""Pops the request context."""
"""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.
"""
self.app.do_teardown_request()
_request_ctx_stack.pop()
def __enter__(self):
@ -99,5 +126,5 @@ class _RequestContext(object):
# the context can be force kept alive for the test client.
# See flask.testing for how this works.
if not self.request.environ.get('flask._preserve_context') and \
(tb is None or not self.app.debug):
(tb is None or not self.app.preserve_context_on_exception):
self.pop()

41
flask/helpers.py

@ -171,7 +171,8 @@ def url_for(endpoint, **values):
==================== ======================= =============================
Variable arguments that are unknown to the target endpoint are appended
to the generated URL as query arguments.
to the generated URL as query arguments. If the value of a query argument
is `None`, the whole pair is skipped.
For more information, head over to the :ref:`Quickstart <url-building>`.
@ -253,7 +254,8 @@ def get_flashed_messages(with_categories=False):
"""
flashes = _request_ctx_stack.top.flashes
if flashes is None:
_request_ctx_stack.top.flashes = flashes = session.pop('_flashes', [])
_request_ctx_stack.top.flashes = flashes = session.pop('_flashes') \
if '_flashes' in session else []
if not with_categories:
return [x[1] for x in flashes]
return flashes
@ -326,7 +328,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
if not attachment_filename and not mimetype \
and isinstance(filename, basestring):
warn(DeprecationWarning('The filename support for file objects '
'passed to send_file is not deprecated. Pass an '
'passed to send_file is now deprecated. Pass an '
'attach_filename if you want mimetypes to be guessed.'),
stacklevel=2)
if add_etags:
@ -393,6 +395,31 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
return rv
def safe_join(directory, filename):
"""Safely join `directory` and `filename`.
Example usage::
@app.route('/wiki/<path:filename>')
def wiki_page(filename):
filename = safe_join(app.config['WIKI_FOLDER'], filename)
with open(filename, 'rb') as fd:
content = fd.read() # Read and process the file content...
:param directory: the base directory.
:param filename: the untrusted filename relative to that directory.
:raises: :class:`~werkzeug.exceptions.NotFound` if the retsulting path
would fall out of `directory`.
"""
filename = posixpath.normpath(filename)
for sep in _os_alt_seps:
if sep in filename:
raise NotFound()
if os.path.isabs(filename) or filename.startswith('../'):
raise NotFound()
return os.path.join(directory, filename)
def send_from_directory(directory, filename, **options):
"""Send a file from a given directory with :func:`send_file`. This
is a secure way to quickly expose static files from an upload folder
@ -420,13 +447,7 @@ def send_from_directory(directory, filename, **options):
:param options: optional keyword arguments that are directly
forwarded to :func:`send_file`.
"""
filename = posixpath.normpath(filename)
for sep in _os_alt_seps:
if sep in filename:
raise NotFound()
if os.path.isabs(filename) or filename.startswith('../'):
raise NotFound()
filename = os.path.join(directory, filename)
filename = safe_join(directory, filename)
if not os.path.isfile(filename):
raise NotFound()
return send_file(filename, conditional=True, **options)

3
flask/logging.py

@ -11,7 +11,7 @@
from __future__ import absolute_import
from logging import getLogger, StreamHandler, Formatter, Logger, DEBUG
from logging import getLogger, StreamHandler, Formatter, getLoggerClass, DEBUG
def create_logger(app):
@ -21,6 +21,7 @@ def create_logger(app):
function also removes all attached handlers in case there was a
logger with the log name before.
"""
Logger = getLoggerClass()
class DebugLogger(Logger):
def getEffectiveLevel(x):

1
flask/signals.py

@ -47,4 +47,5 @@ _signals = Namespace()
template_rendered = _signals.signal('template-rendered')
request_started = _signals.signal('request-started')
request_finished = _signals.signal('request-finished')
request_tearing_down = _signals.signal('request-tearing-down')
got_request_exception = _signals.signal('got-request-exception')

8
flask/wrappers.py

@ -81,7 +81,13 @@ class Request(RequestBase):
if __debug__:
_assert_have_json()
if self.mimetype == 'application/json':
return json.loads(self.data)
request_charset = self.mimetype_params.get('charset')
if request_charset is not None:
j = json.loads(self.data, encoding=request_charset )
else:
j = json.loads(self.data)
return j
class Response(ResponseBase):

78
tests/flask_tests.py

@ -415,37 +415,6 @@ class BasicFunctionalityTestCase(unittest.TestCase):
assert 'after' in evts
assert rv == 'request|after'
def test_after_request_errors(self):
app = flask.Flask(__name__)
called = []
@app.after_request
def after_request(response):
called.append(True)
return response
@app.route('/')
def fails():
1/0
rv = app.test_client().get('/')
assert rv.status_code == 500
assert 'Internal Server Error' in rv.data
assert len(called) == 1
def test_after_request_handler_error(self):
called = []
app = flask.Flask(__name__)
@app.after_request
def after_request(response):
called.append(True)
1/0
return response
@app.route('/')
def fails():
1/0
rv = app.test_client().get('/')
assert rv.status_code == 500
assert 'Internal Server Error' in rv.data
assert len(called) == 1
def test_teardown_request_handler(self):
called = []
app = flask.Flask(__name__)
@ -477,7 +446,6 @@ class BasicFunctionalityTestCase(unittest.TestCase):
assert 'Response' in rv.data
assert len(called) == 1
def test_teardown_request_handler_error(self):
called = []
app = flask.Flask(__name__)
@ -511,7 +479,6 @@ class BasicFunctionalityTestCase(unittest.TestCase):
assert 'Internal Server Error' in rv.data
assert len(called) == 2
def test_before_after_request_order(self):
called = []
app = flask.Flask(__name__)
@ -564,6 +531,19 @@ class BasicFunctionalityTestCase(unittest.TestCase):
assert rv.status_code == 500
assert 'internal server error' == rv.data
def test_teardown_on_pop(self):
buffer = []
app = flask.Flask(__name__)
@app.teardown_request
def end_of_request(exception):
buffer.append(exception)
ctx = app.test_request_context()
ctx.push()
assert buffer == []
ctx.pop()
assert buffer == [None]
def test_response_creation(self):
app = flask.Flask(__name__)
@app.route('/unicode')
@ -659,6 +639,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
app.config.update(
SERVER_NAME='localhost.localdomain:5000'
)
@app.route('/')
def index():
return None
@ -798,9 +779,40 @@ class BasicFunctionalityTestCase(unittest.TestCase):
t.start()
t.join()
def test_max_content_length(self):
app = flask.Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 64
@app.before_request
def always_first():
flask.request.form['myfile']
assert False
@app.route('/accept', methods=['POST'])
def accept_file():
flask.request.form['myfile']
assert False
@app.errorhandler(413)
def catcher(error):
return '42'
c = app.test_client()
rv = c.post('/accept', data={'myfile': 'foo' * 100})
assert rv.data == '42'
class JSONTestCase(unittest.TestCase):
def test_json_body_encoding(self):
app = flask.Flask(__name__)
app.debug = True
@app.route('/')
def index():
return flask.request.json
c = app.test_client()
resp = c.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'),
content_type='application/json; charset=iso-8859-15')
assert resp.data == u'Hällo Wörld'.encode('utf-8')
def test_jsonify(self):
d = dict(a=23, b=42, c=[1, 2, 3])
app = flask.Flask(__name__)

Loading…
Cancel
Save