Skip to content

Commit a6cd8f2

Browse files
committed
document the lifecycle of a flask application and request
1 parent 129568f commit a6cd8f2

File tree

3 files changed

+172
-2
lines changed

3 files changed

+172
-2
lines changed

docs/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ community-maintained extensions to add even more functionality.
4848
config
4949
signals
5050
views
51+
lifecycle
5152
appcontext
5253
reqcontext
5354
blueprints

docs/lifecycle.rst

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
Application Structure and Lifecycle
2+
===================================
3+
4+
Flask makes it pretty easy to write a web application. But there are quite a few
5+
different parts to an application and to each request it handles. Knowing what happens
6+
during application setup, serving, and handling requests will help you know what's
7+
possible in Flask and how to structure your application.
8+
9+
10+
Application Setup
11+
-----------------
12+
13+
The first step in creating a Flask application is creating the application object. Each
14+
Flask application is an instance of the :class:`.Flask` class, which collects all
15+
configuration, extensions, and views.
16+
17+
.. code-block:: python
18+
19+
from flask import Flask
20+
21+
app = Flask(__name__)
22+
app.config.from_mapping(
23+
SECRET_KEY="dev",
24+
)
25+
app.config.from_prefixed_env()
26+
27+
@app.route("/")
28+
def index():
29+
return "Hello, World!"
30+
31+
This is known as the "application setup phase", it's the code you write that's outside
32+
any view functions or other handlers. It can be split up between different modules and
33+
sub-packages, but all code that you want to be part of your application must be imported
34+
in order for it to be registered.
35+
36+
All application setup must be completed before you start serving your application and
37+
handling requests. This is because WSGI servers divide work between multiple workers, or
38+
can be distributed across multiple machines. If the configuration changed in one worker,
39+
there's no way for Flask to ensure consistency between other workers.
40+
41+
Flask tries to help developers catch some of these setup ordering issues by showing an
42+
error if setup-related methods are called after requests are handled. In that case
43+
you'll see this error:
44+
45+
The setup method 'route' can no longer be called on the application. It has already
46+
handled its first request, any changes will not be applied consistently.
47+
Make sure all imports, decorators, functions, etc. needed to set up the application
48+
are done before running it.
49+
50+
However, it is not possible for Flask to detect all cases of out-of-order setup. In
51+
general, don't do anything to modify the ``Flask`` app object and ``Blueprint`` objects
52+
from within view functions that run during requests. This includes:
53+
54+
- Adding routes, view functions, and other request handlers with ``@app.route``,
55+
``@app.errorhandler``, ``@app.before_request``, etc.
56+
- Registering blueprints.
57+
- Loading configuration with ``app.config``.
58+
- Setting up the Jinja template environment with ``app.jinja_env``.
59+
- Setting a session interface, instead of the default itsdangerous cookie.
60+
- Setting a JSON provider with ``app.json``, instead of the default provider.
61+
- Creating and initializing Flask extensions.
62+
63+
64+
Serving the Application
65+
-----------------------
66+
67+
Flask is a WSGI application framework. The other half of WSGI is the WSGI server. During
68+
development, Flask, through Werkzeug, provides a development WSGI server with the
69+
``flask run`` CLI command. When you are done with development, use a production server
70+
to serve your application, see :doc:`deploying/index`.
71+
72+
Regardless of what server you're using, it will follow the :pep:`3333` WSGI spec. The
73+
WSGI server will be told how to access your Flask application object, which is the WSGI
74+
application. Then it will start listening for HTTP requests, translate the request data
75+
into a WSGI environ, and call the WSGI application with that data. The WSGI application
76+
will return data that is translated into an HTTP response.
77+
78+
#. Browser or other client makes HTTP request.
79+
#. WSGI server receives request.
80+
#. WSGI server converts HTTP data to WSGI ``environ`` dict.
81+
#. WSGI server calls WSGI application with the ``environ``.
82+
#. Flask, the WSGI application, does all its internal processing to route the request
83+
to a view function, handle errors, etc.
84+
#. Flask translates View function return into WSGI response data, passes it to WSGI
85+
server.
86+
#. WSGI server creates and send an HTTP response.
87+
#. Client receives the HTTP response.
88+
89+
90+
Middleware
91+
~~~~~~~~~~
92+
93+
The WSGI application above is a callable that behaves in a certain way. Middleware
94+
is a WSGI application that wraps another WSGI application. It's a similar concept to
95+
Python decorators. The outermost middleware will be called by the server. It can modify
96+
the data passed to it, then call the WSGI application (or further middleware) that it
97+
wraps, and so on. And it can take the return value of that call and modify it further.
98+
99+
From the WSGI server's perspective, there is one WSGI application, the one it calls
100+
directly. Typically, Flask is the "real" application at the end of the chain of
101+
middleware. But even Flask can call further WSGI applications, although that's an
102+
advanced, uncommon use case.
103+
104+
A common middleware you'll see used with Flask is Werkzeug's
105+
:class:`~werkzeug.middleware.proxy_fix.ProxyFix`, which modifies the request to look
106+
like it came directly from a client even if it passed through HTTP proxies on the way.
107+
There are other middleware that can handle serving static files, authentication, etc.
108+
109+
110+
How a Request is Handled
111+
------------------------
112+
113+
For us, the interesting part of the steps above is when Flask gets called by the WSGI
114+
server (or middleware). At that point, it will do quite a lot to handle the request and
115+
generate the response. At the most basic, it will match the URL to a view function, call
116+
the view function, and pass the return value back to the server. But there are many more
117+
parts that you can use to customize its behavior.
118+
119+
#. WSGI server calls the Flask object, which calls :meth:`.Flask.wsgi_app`.
120+
#. A :class:`.RequestContext` object is created. This converts the WSGI ``environ``
121+
dict into a :class:`.Request` object. It also creates an :class:`AppContext` object.
122+
#. The :doc:`app context <appcontext>` is pushed, which makes :data:`.current_app` and
123+
:data:`.g` available.
124+
#. The :data:`.appcontext_pushed` signal is sent.
125+
#. The :doc:`request context <reqcontext>` is pushed, which makes :attr:`.request` and
126+
:class:`.session` available.
127+
#. The session is opened, loading any existing session data using the app's
128+
:attr:`~.Flask.session_interface`, an instance of :class:`.SessionInterface`.
129+
#. The URL is matched against the URL rules registered with the :meth:`~.Flask.route`
130+
decorator during application setup. If there is no match, the error - usually a 404,
131+
405, or redirect - is stored to be handled later.
132+
#. The :data:`.request_started` signal is sent.
133+
#. Any :meth:`~.Flask.url_value_preprocessor` decorated functions are called.
134+
#. Any :meth:`~.Flask.before_request` decorated functions are called. If any of
135+
these function returns a value it is treated as the response immediately.
136+
#. If the URL didn't match a route a few steps ago, that error is raised now.
137+
#. The :meth:`~.Flask.route` decorated view function associated with the matched URL
138+
is called and returns a value to be used as the response.
139+
#. If any step so far raised an exception, and there is an :meth:`~.Flask.errorhandler`
140+
decorated function that matches the exception class or HTTP error code, it is
141+
called to handle the error and return a response.
142+
#. Whatever returned a response value - a before request function, the view, or an
143+
error handler, that value is converted to a :class:`.Response` object.
144+
#. Any :func:`~.after_this_request` decorated functions are called, then cleared.
145+
#. Any :meth:`~.Flask.after_request` decorated functions are called, which can modify
146+
the response object.
147+
#. The session is saved, persisting any modified session data using the app's
148+
:attr:`~.Flask.session_interface`.
149+
#. The :data:`.request_finished` signal is sent.
150+
#. If any step so far raised an exception, and it was not handled by an error handler
151+
function, it is handled now. HTTP exceptions are treated as responses with their
152+
corresponding status code, other exceptions are converted to a generic 500 response.
153+
The :data:`.got_request_exception` signal is sent.
154+
#. The response object's status, headers, and body are returned to the WSGI server.
155+
#. Any :meth:`~.Flask.teardown_request` decorated functions are called.
156+
#. The :data:`.request_tearing_down` signal is sent.
157+
#. The request context is popped, :attr:`.request` and :class:`.session` are no longer
158+
available.
159+
#. Any :meth:`~.Flask.teardown_appcontext` decorated functions are called.
160+
#. The :data:`.appcontext_tearing_down` signal is sent.
161+
#. The app context is popped, :data:`.current_app` and :data:`.g` are no longer
162+
available.
163+
#. The :data:`.appcontext_popped` signal is sent.
164+
165+
There are even more decorators and customization points than this, but that aren't part
166+
of every request lifecycle. They're more specific to certain things you might use during
167+
a request, such as templates, building URLs, or handling JSON data. See the rest of this
168+
documentation, as well as the :doc:`api` to explore further.

docs/reqcontext.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,12 @@ everything that runs in the block will have access to :data:`request`,
6969
populated with your test data. ::
7070

7171
def generate_report(year):
72-
format = request.args.get('format')
72+
format = request.args.get("format")
7373
...
7474

7575
with app.test_request_context(
76-
'/make_report/2017', data={'format': 'short'}):
76+
"/make_report/2017", query_string={"format": "short"}
77+
):
7778
generate_report()
7879

7980
If you see that error somewhere else in your code not related to

0 commit comments

Comments
 (0)