forked from orbit-oss/flask
Merge branch 'new-request-dispatching' into blueprints
This commit is contained in:
commit
673fa18e6d
28 changed files with 689 additions and 266 deletions
10
CHANGES
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
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)
|
||||
|
|
|
|||
|
|
@ -51,27 +51,36 @@ 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
|
||||
propagation of exceptions. If not set or
|
||||
explicitly set to `None` this is
|
||||
implicitly true if either `TESTING` or
|
||||
`DEBUG` is true.
|
||||
``SECRET_KEY`` the secret key
|
||||
``SESSION_COOKIE_NAME`` the name of the session cookie
|
||||
``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as
|
||||
:class:`datetime.timedelta` object.
|
||||
``USE_X_SENDFILE`` enable/disable x-sendfile
|
||||
``LOGGER_NAME`` the name of the logger
|
||||
``SERVER_NAME`` the name of the server. Required for
|
||||
subdomain support (e.g.: ``'localhost'``)
|
||||
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
|
||||
reject incoming requests with a
|
||||
content length greater than this by
|
||||
returning a 413 status code.
|
||||
=============================== =========================================
|
||||
================================= =========================================
|
||||
``DEBUG`` enable/disable debug mode
|
||||
``TESTING`` enable/disable testing mode
|
||||
``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the
|
||||
propagation of exceptions. If not set or
|
||||
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
|
||||
:class:`datetime.timedelta` object.
|
||||
``USE_X_SENDFILE`` enable/disable x-sendfile
|
||||
``LOGGER_NAME`` the name of the logger
|
||||
``SERVER_NAME`` the name of the server. Required for
|
||||
subdomain support (e.g.: ``'localhost'``)
|
||||
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
|
||||
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/
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ instructions for web development with Flask.
|
|||
errorhandling
|
||||
config
|
||||
signals
|
||||
reqcontext
|
||||
shell
|
||||
patterns/index
|
||||
deploying/index
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-----------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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,12 +53,12 @@ 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
|
||||
# python interpreter
|
||||
run('/var/www/yourapplication/env/bin/python setup.py install')
|
||||
# now setup the package with our virtual environment's
|
||||
# python interpreter
|
||||
run('/var/www/yourapplication/env/bin/python setup.py install')
|
||||
# now that all is set up, delete the folder again
|
||||
run('rm -rf /tmp/yourapplication /tmp/yourapplication.tar.gz')
|
||||
# and finally touch the .wsgi file so that mod_wsgi triggers
|
||||
|
|
|
|||
|
|
@ -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`::
|
||||
|
||||
|
|
|
|||
|
|
@ -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`)::
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Normal file
230
docs/reqcontext.rst
Normal file
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
Generally it's recommended that you read the :ref:`request-context`
|
||||
chapter of the documentation first.
|
||||
|
||||
Creating a Request Context
|
||||
--------------------------
|
||||
|
||||
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::
|
||||
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`:
|
||||
|
||||
from flask import request, url_for
|
||||
>>> ctx = app.test_request_context()
|
||||
|
||||
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 request context:
|
||||
|
||||
>>> ctx = app.test_request_context('/?next=http://example.com/')
|
||||
|
||||
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
|
||||
--------------------------------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-----------
|
||||
|
||||
|
|
|
|||
|
|
@ -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('/')
|
||||
|
|
|
|||
|
|
@ -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('/')
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
157
flask/app.py
157
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,21 +879,41 @@ 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
|
||||
if req.routing_exception is not None:
|
||||
raise req.routing_exception
|
||||
rule = req.url_rule
|
||||
# if we provide automatic options for this URL and the
|
||||
# request came with the OPTIONS method, reply automatically
|
||||
if getattr(rule, 'provide_automatic_options', False) \
|
||||
and req.method == 'OPTIONS':
|
||||
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:
|
||||
if req.routing_exception is not None:
|
||||
raise req.routing_exception
|
||||
rule = req.url_rule
|
||||
# if we provide automatic options for this URL and the
|
||||
# request came with the OPTIONS method, reply automatically
|
||||
if getattr(rule, 'provide_automatic_options', False) \
|
||||
and req.method == 'OPTIONS':
|
||||
return self.make_default_options_response()
|
||||
# otherwise dispatch to the handler for that endpoint
|
||||
return self.view_functions[rule.endpoint](**req.view_args)
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -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
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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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…
Add table
Add a link
Reference in a new issue