Browse Source

Rewrote tutorial to use the g based appcontext object

pull/843/merge
Armin Ronacher 11 years ago
parent
commit
05161d3584
  1. 1
      docs/index.rst
  2. 106
      docs/tutorial/dbcon.rst
  3. 49
      docs/tutorial/dbinit.rst
  4. 2
      docs/tutorial/index.rst
  5. 70
      docs/tutorial/setup.rst
  6. 16
      docs/tutorial/views.rst
  7. 44
      examples/flaskr/flaskr.py

1
docs/index.rst

@ -24,6 +24,7 @@ following links:
- `Jinja2 Documentation <http://jinja.pocoo.org/2/documentation/>`_
- `Werkzeug Documentation <http://werkzeug.pocoo.org/documentation/>`_
.. _Jinja2: http://jinja.pocoo.org/2/
.. _Werkzeug: http://werkzeug.pocoo.org/

106
docs/tutorial/dbcon.rst

@ -1,56 +1,72 @@
.. _tutorial-dbcon:
Step 4: Request Database Connections
------------------------------------
Now we know how we can open database connections and use them for scripts,
but how can we elegantly do that for requests? We will need the database
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::
@app.before_request
def before_request():
g.db = connect_db()
@app.teardown_request
def teardown_request(exception):
db = getattr(g, 'db', None)
if db is not None:
db.close()
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
passed the response that will be sent to the client. They have to return
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`
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
the scenes to ensure it does the right thing.
For an even better way to handle such resources see the :ref:`sqlite3`
documentation.
Continue to :ref:`tutorial-views`.
Step 3: Database Connections
----------------------------
We have created a function for establishing a database connection with
`create_db` but by itself that's not particularly useful. Creating and
closing database connections all the time is very inefficient, so we want
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?
This is where the application context comes into play. So let's start
there.
Flask provides us with two contexts: the application context and the
request context. For the time being all you have to know is that there
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.
For the time being all you have to know is that you can store information
savely on the :data:`~flask.g` object.
So when do you put it on there? To do that you can make a helper
function. The first time the function is called it will create a database
connection for the current context and successive calls will return the
already established connection::
def get_db():
"""Opens a new database connection if there is none yet for the
current application context.
"""
if not hasattr(g, 'sqlite_db'):
g.sqlite_db = connect_db()
return g.sqlite_db
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?
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
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).
If you need a moment to find your bearings, take a look at how the `example

49
docs/tutorial/dbinit.rst

@ -1,6 +1,6 @@
.. _tutorial-dbinit:
Step 3: Creating The Database
Step 4: Creating The Database
=============================
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
for you to the application.
If you want to do that, you first have to import the
:func:`contextlib.closing` function from the contextlib package.
Accordingly, add the following lines to your existing imports 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`::
To do this we can create a function called `init_db` that initializes the
database. Let me show you the code first. Just add that function below
the `connect_db` function in `flaskr.py`::
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:
db.cursor().executescript(f.read())
db.commit()
The :func:`~contextlib.closing` helper function allows us to keep a
connection open for the duration of the `with` block. The
:func:`~flask.Flask.open_resource` method of the application object
supports that functionality out of the box, so it can be used in the
`with` block directly. This function opens a file from the resource
So what's happening here? Remember how we learned last chapter that the
application context is created every time a request comes in? Here we
don't have a request yet, so we need to create the application context by
hand. Without an application context the :data:`~flask.g` object does not
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
using this here to execute a script on the database connection.
When we connect to a database we get a connection object (here called
`db`) that can give us a cursor. On that cursor there is a method to
execute a complete script. Finally we only have to commit the changes.
SQLite 3 and other transactional databases will not commit unless you
explicitly tell it to.
The connection object provided by SQLite can give us a cursor object.
On that cursor there is a method to execute a complete script. Finally we
only have to commit the changes. SQLite 3 and other transactional
databases will not commit unless you explicitly tell it to.
Now it is possible to create a database by starting up a Python shell and
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
correct (singular vs. plural for example).
Continue with :ref:`tutorial-dbcon`
Continue with :ref:`tutorial-views`

2
docs/tutorial/index.rst

@ -24,8 +24,8 @@ the `example source`_.
folders
schema
setup
dbinit
dbcon
dbinit
views
templates
css

70
docs/tutorial/setup.rst

@ -5,31 +5,18 @@ Step 2: Application Setup Code
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
will add the imports we will need as well as the config section. For
small applications it's a possibility to drop the configuration directly
into the module which we will be doing here. However a cleaner solution
would be to create a separate `.ini` or `.py` file and load that or import
the values from there.
will add the imports and create the application object. For small
applications it's a possibility to drop the configuration directly into
the module which we will be doing here. However a cleaner solution would
be to create a separate `.ini` or `.py` file and load that or import the
values from there.
In `flaskr.py`::
First we add the imports in `flaskr.py`::
# all the imports
import sqlite3
from flask import Flask, request, session, g, redirect, url_for, \
abort, 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.
from flask import Flask, request, session, g, redirect, url_for, abort, \
render_template, flash
Next we can create our actual application and initialize it with the
config from the same file, in `flaskr.py`::
@ -38,10 +25,24 @@ config from the same file, in `flaskr.py`::
app = Flask(__name__)
app.config.from_object(__name__)
:meth:`~flask.Config.from_object` will look at the given object (if it's a
string it will import it) and then look for all uppercase variables
defined there. In our case, the configuration we just wrote a few lines
of code above. You can also move that into a separate file.
# 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)
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
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
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
debug flag enables or disables the interactive debugger. *Never leave
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
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():
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
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
information.
Continue with :ref:`tutorial-dbinit`.
Continue with :ref:`tutorial-dbcon`.

16
docs/tutorial/views.rst

@ -12,18 +12,17 @@ Show Entries
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.
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
in the select statement. This is good enough for small applications like
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.
returned from the cursor look a bit like tuples because we are using
the :class:`sqlite3.Row` row factory.
The view function will pass the entries as dicts to the
`show_entries.html` template and return the rendered one::
@app.route('/')
def show_entries():
cur = g.db.execute('select title, text from entries order by id desc')
entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()]
db = get_db()
cur = db.execute('select title, text from entries order by id desc')
entries = cur.fetchall()
return render_template('show_entries.html', entries=entries)
Add New Entry
@ -39,9 +38,10 @@ redirect back to the `show_entries` page::
def add_entry():
if not session.get('logged_in'):
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']])
g.db.commit()
db.commit()
flash('New entry was successfully posted')
return redirect(url_for('show_entries'))

44
examples/flaskr/flaskr.py

@ -11,22 +11,31 @@
"""
from sqlite3 import dbapi2 as sqlite3
from flask import Flask, request, session, redirect, url_for, abort, \
render_template, flash, _app_ctx_stack
from flask import Flask, request, session, g, redirect, url_for, abort, \
render_template, flash
# configuration
DATABASE = '/tmp/flaskr.db'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'
# create our little application :)
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)
def connect_db():
"""Connects to the specific database."""
rv = sqlite3.connect(app.config['DATABASE'])
rv.row_factory = sqlite3.Row
return rv
def init_db():
"""Creates the database tables."""
with app.app_context():
@ -40,21 +49,16 @@ def get_db():
"""Opens a new database connection if there is none yet for the
current application context.
"""
top = _app_ctx_stack.top
if not hasattr(top, 'sqlite_db'):
sqlite_db = sqlite3.connect(app.config['DATABASE'])
sqlite_db.row_factory = sqlite3.Row
top.sqlite_db = sqlite_db
return top.sqlite_db
if not hasattr(g, 'sqlite_db'):
g.sqlite_db = connect_db()
return g.sqlite_db
@app.teardown_appcontext
def close_db_connection(exception):
def close_db(error):
"""Closes the database again at the end of the request."""
top = _app_ctx_stack.top
if hasattr(top, 'sqlite_db'):
top.sqlite_db.close()
if hasattr(g, 'sqlite_db'):
g.sqlite_db.close()
@app.route('/')

Loading…
Cancel
Save