mirror of https://github.com/pallets/flask.git
Rewrote tutorial to use the g based appcontext object
This commit is contained in:
parent
42cf782ee3
commit
05161d3584
|
@ -24,6 +24,7 @@ following links:
|
||||||
- `Jinja2 Documentation <http://jinja.pocoo.org/2/documentation/>`_
|
- `Jinja2 Documentation <http://jinja.pocoo.org/2/documentation/>`_
|
||||||
- `Werkzeug Documentation <http://werkzeug.pocoo.org/documentation/>`_
|
- `Werkzeug Documentation <http://werkzeug.pocoo.org/documentation/>`_
|
||||||
|
|
||||||
|
|
||||||
.. _Jinja2: http://jinja.pocoo.org/2/
|
.. _Jinja2: http://jinja.pocoo.org/2/
|
||||||
.. _Werkzeug: http://werkzeug.pocoo.org/
|
.. _Werkzeug: http://werkzeug.pocoo.org/
|
||||||
|
|
||||||
|
|
|
@ -1,56 +1,72 @@
|
||||||
.. _tutorial-dbcon:
|
.. _tutorial-dbcon:
|
||||||
|
|
||||||
Step 4: Request Database Connections
|
Step 3: Database Connections
|
||||||
------------------------------------
|
----------------------------
|
||||||
|
|
||||||
Now we know how we can open database connections and use them for scripts,
|
We have created a function for establishing a database connection with
|
||||||
but how can we elegantly do that for requests? We will need the database
|
`create_db` but by itself that's not particularly useful. Creating and
|
||||||
connection in all our functions so it makes sense to initialize them
|
closing database connections all the time is very inefficient, so we want
|
||||||
before each request and shut them down afterwards.
|
to keep it around for longer. Because database connections encapsulate a
|
||||||
|
transaction we also need to make sure that only one request at the time
|
||||||
|
uses the connection. So how can we elegantly do that with Flask?
|
||||||
|
|
||||||
Flask allows us to do that with the :meth:`~flask.Flask.before_request`,
|
This is where the application context comes into play. So let's start
|
||||||
:meth:`~flask.Flask.after_request` and :meth:`~flask.Flask.teardown_request`
|
there.
|
||||||
decorators::
|
|
||||||
|
|
||||||
@app.before_request
|
Flask provides us with two contexts: the application context and the
|
||||||
def before_request():
|
request context. For the time being all you have to know is that there
|
||||||
g.db = connect_db()
|
are special variables that use these. For instance the
|
||||||
|
:data:`~flask.request` variable is the request object associated with
|
||||||
|
the current request, whereas :data:`~flask.g` is a general purpose
|
||||||
|
variable associated with the current application context. We will go into
|
||||||
|
the details of this a bit later.
|
||||||
|
|
||||||
@app.teardown_request
|
For the time being all you have to know is that you can store information
|
||||||
def teardown_request(exception):
|
savely on the :data:`~flask.g` object.
|
||||||
db = getattr(g, 'db', None)
|
|
||||||
if db is not None:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
Functions marked with :meth:`~flask.Flask.before_request` are called before
|
So when do you put it on there? To do that you can make a helper
|
||||||
a request and passed no arguments. Functions marked with
|
function. The first time the function is called it will create a database
|
||||||
:meth:`~flask.Flask.after_request` are called after a request and
|
connection for the current context and successive calls will return the
|
||||||
passed the response that will be sent to the client. They have to return
|
already established connection::
|
||||||
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.
|
|
||||||
|
|
||||||
We store our current database connection on the special :data:`~flask.g`
|
def get_db():
|
||||||
object that Flask provides for us. This object stores information for one
|
"""Opens a new database connection if there is none yet for the
|
||||||
request only and is available from within each function. Never store such
|
current application context.
|
||||||
things on other objects because this would not work with threaded
|
"""
|
||||||
environments. That special :data:`~flask.g` object does some magic behind
|
if not hasattr(g, 'sqlite_db'):
|
||||||
the scenes to ensure it does the right thing.
|
g.sqlite_db = connect_db()
|
||||||
|
return g.sqlite_db
|
||||||
|
|
||||||
For an even better way to handle such resources see the :ref:`sqlite3`
|
|
||||||
documentation.
|
|
||||||
|
|
||||||
Continue to :ref:`tutorial-views`.
|
So now we know how to connect, but how do we properly disconnect? For
|
||||||
|
that flask provides us with the :meth:`~flask.Flask.teardown_appcontext`
|
||||||
|
decorator. It's executed every time the application context tears down::
|
||||||
|
|
||||||
|
@app.teardown_appcontext
|
||||||
|
def close_db(error):
|
||||||
|
"""Closes the database again at the end of the request."""
|
||||||
|
if hasattr(g, 'sqlite_db'):
|
||||||
|
g.sqlite_db.close()
|
||||||
|
|
||||||
|
Functions marked with :meth:`~flask.Flask.teardown_appcontext` are called
|
||||||
|
every time the app context tears down. So what does this mean?
|
||||||
|
Essentially the app context is created before the request comes in and is
|
||||||
|
destroyed (teared down) whenever the request finishes. A teardown can
|
||||||
|
happen because of two reasons: either everything went well (the error
|
||||||
|
parameter will be `None`) or an exception happend in which case the error
|
||||||
|
is passed to the teardown function.
|
||||||
|
|
||||||
|
Curious about what these contexts mean? Have a look at the
|
||||||
|
:ref:`app-context` documentation to learn more.
|
||||||
|
|
||||||
|
Continue to :ref:`tutorial-dbinit`.
|
||||||
|
|
||||||
.. hint:: Where do I put this code?
|
.. hint:: Where do I put this code?
|
||||||
|
|
||||||
If you've been following along in this tutorial, you might be wondering
|
If you've been following along in this tutorial, you might be wondering
|
||||||
where to put the code from this step and the next. A logical place is to
|
where to put the code from this step and the next. A logical place is to
|
||||||
group these module-level functions together, and put your new
|
group these module-level functions together, and put your new
|
||||||
``before_request`` and ``teardown_request`` functions below your existing
|
``get_db`` and ``close_db`` functions below your existing
|
||||||
``init_db`` function (following the tutorial line-by-line).
|
``init_db`` function (following the tutorial line-by-line).
|
||||||
|
|
||||||
If you need a moment to find your bearings, take a look at how the `example
|
If you need a moment to find your bearings, take a look at how the `example
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.. _tutorial-dbinit:
|
.. _tutorial-dbinit:
|
||||||
|
|
||||||
Step 3: Creating The Database
|
Step 4: Creating The Database
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
Flaskr is a database powered application as outlined earlier, and more
|
Flaskr is a database powered application as outlined earlier, and more
|
||||||
|
@ -20,36 +20,39 @@ to provide the path to the database there which leaves some place for
|
||||||
errors. It's a good idea to add a function that initializes the database
|
errors. It's a good idea to add a function that initializes the database
|
||||||
for you to the application.
|
for you to the application.
|
||||||
|
|
||||||
If you want to do that, you first have to import the
|
To do this we can create a function called `init_db` that initializes the
|
||||||
:func:`contextlib.closing` function from the contextlib package.
|
database. Let me show you the code first. Just add that function below
|
||||||
Accordingly, add the following lines to your existing imports in `flaskr.py`::
|
the `connect_db` function in `flaskr.py`::
|
||||||
|
|
||||||
from contextlib import closing
|
|
||||||
|
|
||||||
Next we can create a function called `init_db` that initializes the
|
|
||||||
database. For this we can use the `connect_db` function we defined
|
|
||||||
earlier. Just add that function below the `connect_db` function in
|
|
||||||
`flaskr.py`::
|
|
||||||
|
|
||||||
def init_db():
|
def init_db():
|
||||||
with closing(connect_db()) as db:
|
app app.app_context():
|
||||||
|
db = get_db()
|
||||||
with app.open_resource('schema.sql', mode='r') as f:
|
with app.open_resource('schema.sql', mode='r') as f:
|
||||||
db.cursor().executescript(f.read())
|
db.cursor().executescript(f.read())
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
The :func:`~contextlib.closing` helper function allows us to keep a
|
So what's happening here? Remember how we learned last chapter that the
|
||||||
connection open for the duration of the `with` block. The
|
application context is created every time a request comes in? Here we
|
||||||
:func:`~flask.Flask.open_resource` method of the application object
|
don't have a request yet, so we need to create the application context by
|
||||||
supports that functionality out of the box, so it can be used in the
|
hand. Without an application context the :data:`~flask.g` object does not
|
||||||
`with` block directly. This function opens a file from the resource
|
know yet to which application it becomes as there could be more than one!
|
||||||
|
|
||||||
|
The ``with app.app_context()`` statement establishes the application
|
||||||
|
context for us. In the body of the with statement the :flask:`~flask.g`
|
||||||
|
object will be associated with ``app``. At the end of the with statement
|
||||||
|
the association is released and all teardown functions are executed. This
|
||||||
|
means that our database connection is disconnected after the commit.
|
||||||
|
|
||||||
|
The :func:`~flask.Flask.open_resource` method of the application object
|
||||||
|
is a convenient helper function that will open a resource that the
|
||||||
|
application provides. This function opens a file from the resource
|
||||||
location (your `flaskr` folder) and allows you to read from it. We are
|
location (your `flaskr` folder) and allows you to read from it. We are
|
||||||
using this here to execute a script on the database connection.
|
using this here to execute a script on the database connection.
|
||||||
|
|
||||||
When we connect to a database we get a connection object (here called
|
The connection object provided by SQLite can give us a cursor object.
|
||||||
`db`) that can give us a cursor. On that cursor there is a method to
|
On that cursor there is a method to execute a complete script. Finally we
|
||||||
execute a complete script. Finally we only have to commit the changes.
|
only have to commit the changes. SQLite 3 and other transactional
|
||||||
SQLite 3 and other transactional databases will not commit unless you
|
databases will not commit unless you explicitly tell it to.
|
||||||
explicitly tell it to.
|
|
||||||
|
|
||||||
Now it is possible to create a database by starting up a Python shell and
|
Now it is possible to create a database by starting up a Python shell and
|
||||||
importing and calling that function::
|
importing and calling that function::
|
||||||
|
@ -63,4 +66,4 @@ importing and calling that function::
|
||||||
you did call the `init_db` function and that your table names are
|
you did call the `init_db` function and that your table names are
|
||||||
correct (singular vs. plural for example).
|
correct (singular vs. plural for example).
|
||||||
|
|
||||||
Continue with :ref:`tutorial-dbcon`
|
Continue with :ref:`tutorial-views`
|
||||||
|
|
|
@ -24,8 +24,8 @@ the `example source`_.
|
||||||
folders
|
folders
|
||||||
schema
|
schema
|
||||||
setup
|
setup
|
||||||
dbinit
|
|
||||||
dbcon
|
dbcon
|
||||||
|
dbinit
|
||||||
views
|
views
|
||||||
templates
|
templates
|
||||||
css
|
css
|
||||||
|
|
|
@ -5,31 +5,18 @@ Step 2: Application Setup Code
|
||||||
|
|
||||||
Now that we have the schema in place we can create the application module.
|
Now that we have the schema in place we can create the application module.
|
||||||
Let's call it `flaskr.py` inside the `flaskr` folder. For starters we
|
Let's call it `flaskr.py` inside the `flaskr` folder. For starters we
|
||||||
will add the imports we will need as well as the config section. For
|
will add the imports and create the application object. For small
|
||||||
small applications it's a possibility to drop the configuration directly
|
applications it's a possibility to drop the configuration directly into
|
||||||
into the module which we will be doing here. However a cleaner solution
|
the module which we will be doing here. However a cleaner solution would
|
||||||
would be to create a separate `.ini` or `.py` file and load that or import
|
be to create a separate `.ini` or `.py` file and load that or import the
|
||||||
the values from there.
|
values from there.
|
||||||
|
|
||||||
In `flaskr.py`::
|
First we add the imports in `flaskr.py`::
|
||||||
|
|
||||||
# all the imports
|
# all the imports
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from flask import Flask, request, session, g, redirect, url_for, \
|
from flask import Flask, request, session, g, redirect, url_for, abort, \
|
||||||
abort, render_template, flash
|
render_template, flash
|
||||||
|
|
||||||
# configuration
|
|
||||||
DATABASE = '/tmp/flaskr.db'
|
|
||||||
DEBUG = True
|
|
||||||
SECRET_KEY = 'development key'
|
|
||||||
USERNAME = 'admin'
|
|
||||||
PASSWORD = 'default'
|
|
||||||
|
|
||||||
.. admonition:: Windows
|
|
||||||
|
|
||||||
If you are on Windows, replace `/tmp/flaskr.db` with a different writeable
|
|
||||||
path of your choice, in the configuration and for the rest of this
|
|
||||||
tutorial.
|
|
||||||
|
|
||||||
Next we can create our actual application and initialize it with the
|
Next we can create our actual application and initialize it with the
|
||||||
config from the same file, in `flaskr.py`::
|
config from the same file, in `flaskr.py`::
|
||||||
|
@ -38,10 +25,24 @@ config from the same file, in `flaskr.py`::
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.from_object(__name__)
|
app.config.from_object(__name__)
|
||||||
|
|
||||||
:meth:`~flask.Config.from_object` will look at the given object (if it's a
|
# Load default config and override config from an environment variable
|
||||||
string it will import it) and then look for all uppercase variables
|
app.config.update(dict(
|
||||||
defined there. In our case, the configuration we just wrote a few lines
|
DATABASE='/tmp/flaskr.db',
|
||||||
of code above. You can also move that into a separate file.
|
DEBUG=True,
|
||||||
|
SECRET_KEY='development key',
|
||||||
|
USERNAME='admin',
|
||||||
|
PASSWORD='default'
|
||||||
|
))
|
||||||
|
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
||||||
|
|
||||||
|
The :class:`~flask.Config` object works similar to a dictionary so we
|
||||||
|
can update it with new values.
|
||||||
|
|
||||||
|
.. admonition:: Windows
|
||||||
|
|
||||||
|
If you are on Windows, replace `/tmp/flaskr.db` with a different writeable
|
||||||
|
path of your choice, in the configuration and for the rest of this
|
||||||
|
tutorial.
|
||||||
|
|
||||||
Usually, it is a good idea to load a separate, environment specific
|
Usually, it is a good idea to load a separate, environment specific
|
||||||
configuration file. Flask allows you to import multiple configurations and it
|
configuration file. Flask allows you to import multiple configurations and it
|
||||||
|
@ -54,7 +55,12 @@ Simply define the environment variable :envvar:`FLASKR_SETTINGS` that points to
|
||||||
a config file to be loaded. The silent switch just tells Flask to not complain
|
a config file to be loaded. The silent switch just tells Flask to not complain
|
||||||
if no such environment key is set.
|
if no such environment key is set.
|
||||||
|
|
||||||
The `secret_key` is needed to keep the client-side sessions secure.
|
In addition to that you can use the :meth:`~flask.Config.from_object`
|
||||||
|
method on the config object and provide it with an import name of a
|
||||||
|
module. Flask will the initialize the variable from that module. Note
|
||||||
|
that in all cases only variable names that are uppercase are considered.
|
||||||
|
|
||||||
|
The ``SECRET_KEY`` is needed to keep the client-side sessions secure.
|
||||||
Choose that key wisely and as hard to guess and complex as possible. The
|
Choose that key wisely and as hard to guess and complex as possible. The
|
||||||
debug flag enables or disables the interactive debugger. *Never leave
|
debug flag enables or disables the interactive debugger. *Never leave
|
||||||
debug mode activated in a production system*, because it will allow users to
|
debug mode activated in a production system*, because it will allow users to
|
||||||
|
@ -62,12 +68,18 @@ execute code on the server!
|
||||||
|
|
||||||
We also add a method to easily connect to the database specified. That
|
We also add a method to easily connect to the database specified. That
|
||||||
can be used to open a connection on request and also from the interactive
|
can be used to open a connection on request and also from the interactive
|
||||||
Python shell or a script. This will come in handy later.
|
Python shell or a script. This will come in handy later. We create a
|
||||||
|
simple database connection through SQLite and then tell it to use the
|
||||||
|
:class:`sqlite3.Row` object to represent rows. This allows us to treat
|
||||||
|
the rows as if they were dictionaries instead of tuples.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
def connect_db():
|
def connect_db():
|
||||||
return sqlite3.connect(app.config['DATABASE'])
|
"""Connects to the specific database."""
|
||||||
|
rv = sqlite3.connect(app.config['DATABASE'])
|
||||||
|
rv.row_factory = sqlite3.Row
|
||||||
|
return rv
|
||||||
|
|
||||||
Finally we just add a line to the bottom of the file that fires up the
|
Finally we just add a line to the bottom of the file that fires up the
|
||||||
server if we want to run that file as a standalone application::
|
server if we want to run that file as a standalone application::
|
||||||
|
@ -93,4 +105,4 @@ focus on that a little later. First we should get the database working.
|
||||||
:ref:`externally visible server <public-server>` section for more
|
:ref:`externally visible server <public-server>` section for more
|
||||||
information.
|
information.
|
||||||
|
|
||||||
Continue with :ref:`tutorial-dbinit`.
|
Continue with :ref:`tutorial-dbcon`.
|
||||||
|
|
|
@ -12,18 +12,17 @@ Show Entries
|
||||||
This view shows all the entries stored in the database. It listens on the
|
This view shows all the entries stored in the database. It listens on the
|
||||||
root of the application and will select title and text from the database.
|
root of the application and will select title and text from the database.
|
||||||
The one with the highest id (the newest entry) will be on top. The rows
|
The one with the highest id (the newest entry) will be on top. The rows
|
||||||
returned from the cursor are tuples with the columns ordered like specified
|
returned from the cursor look a bit like tuples because we are using
|
||||||
in the select statement. This is good enough for small applications like
|
the :class:`sqlite3.Row` row factory.
|
||||||
here, but you might want to convert them into a dict. If you are
|
|
||||||
interested in how to do that, check out the :ref:`easy-querying` example.
|
|
||||||
|
|
||||||
The view function will pass the entries as dicts to the
|
The view function will pass the entries as dicts to the
|
||||||
`show_entries.html` template and return the rendered one::
|
`show_entries.html` template and return the rendered one::
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def show_entries():
|
def show_entries():
|
||||||
cur = g.db.execute('select title, text from entries order by id desc')
|
db = get_db()
|
||||||
entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()]
|
cur = db.execute('select title, text from entries order by id desc')
|
||||||
|
entries = cur.fetchall()
|
||||||
return render_template('show_entries.html', entries=entries)
|
return render_template('show_entries.html', entries=entries)
|
||||||
|
|
||||||
Add New Entry
|
Add New Entry
|
||||||
|
@ -39,9 +38,10 @@ redirect back to the `show_entries` page::
|
||||||
def add_entry():
|
def add_entry():
|
||||||
if not session.get('logged_in'):
|
if not session.get('logged_in'):
|
||||||
abort(401)
|
abort(401)
|
||||||
g.db.execute('insert into entries (title, text) values (?, ?)',
|
db = get_db()
|
||||||
|
db.execute('insert into entries (title, text) values (?, ?)',
|
||||||
[request.form['title'], request.form['text']])
|
[request.form['title'], request.form['text']])
|
||||||
g.db.commit()
|
db.commit()
|
||||||
flash('New entry was successfully posted')
|
flash('New entry was successfully posted')
|
||||||
return redirect(url_for('show_entries'))
|
return redirect(url_for('show_entries'))
|
||||||
|
|
||||||
|
|
|
@ -11,22 +11,31 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sqlite3 import dbapi2 as sqlite3
|
from sqlite3 import dbapi2 as sqlite3
|
||||||
from flask import Flask, request, session, redirect, url_for, abort, \
|
from flask import Flask, request, session, g, redirect, url_for, abort, \
|
||||||
render_template, flash, _app_ctx_stack
|
render_template, flash
|
||||||
|
|
||||||
# configuration
|
|
||||||
DATABASE = '/tmp/flaskr.db'
|
|
||||||
DEBUG = True
|
|
||||||
SECRET_KEY = 'development key'
|
|
||||||
USERNAME = 'admin'
|
|
||||||
PASSWORD = 'default'
|
|
||||||
|
|
||||||
# create our little application :)
|
# create our little application :)
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.from_object(__name__)
|
|
||||||
|
# Load default config and override config from an environment variable
|
||||||
|
app.config.update(dict(
|
||||||
|
DATABASE='/tmp/flaskr.db',
|
||||||
|
DEBUG=True,
|
||||||
|
SECRET_KEY='development key',
|
||||||
|
USERNAME='admin',
|
||||||
|
PASSWORD='default'
|
||||||
|
))
|
||||||
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
||||||
|
|
||||||
|
|
||||||
|
def connect_db():
|
||||||
|
"""Connects to the specific database."""
|
||||||
|
rv = sqlite3.connect(app.config['DATABASE'])
|
||||||
|
rv.row_factory = sqlite3.Row
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
def init_db():
|
def init_db():
|
||||||
"""Creates the database tables."""
|
"""Creates the database tables."""
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
|
@ -40,21 +49,16 @@ def get_db():
|
||||||
"""Opens a new database connection if there is none yet for the
|
"""Opens a new database connection if there is none yet for the
|
||||||
current application context.
|
current application context.
|
||||||
"""
|
"""
|
||||||
top = _app_ctx_stack.top
|
if not hasattr(g, 'sqlite_db'):
|
||||||
if not hasattr(top, 'sqlite_db'):
|
g.sqlite_db = connect_db()
|
||||||
sqlite_db = sqlite3.connect(app.config['DATABASE'])
|
return g.sqlite_db
|
||||||
sqlite_db.row_factory = sqlite3.Row
|
|
||||||
top.sqlite_db = sqlite_db
|
|
||||||
|
|
||||||
return top.sqlite_db
|
|
||||||
|
|
||||||
|
|
||||||
@app.teardown_appcontext
|
@app.teardown_appcontext
|
||||||
def close_db_connection(exception):
|
def close_db(error):
|
||||||
"""Closes the database again at the end of the request."""
|
"""Closes the database again at the end of the request."""
|
||||||
top = _app_ctx_stack.top
|
if hasattr(g, 'sqlite_db'):
|
||||||
if hasattr(top, 'sqlite_db'):
|
g.sqlite_db.close()
|
||||||
top.sqlite_db.close()
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
|
|
Loading…
Reference in New Issue