Browse Source

Rewrote tutorial to use the g based appcontext object

pull/843/merge
Armin Ronacher 12 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/>`_ - `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/

106
docs/tutorial/dbcon.rst

@ -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
Flask allows us to do that with the :meth:`~flask.Flask.before_request`, uses the connection. So how can we elegantly do that with Flask?
:meth:`~flask.Flask.after_request` and :meth:`~flask.Flask.teardown_request`
decorators:: This is where the application context comes into play. So let's start
there.
@app.before_request
def before_request(): Flask provides us with two contexts: the application context and the
g.db = connect_db() request context. For the time being all you have to know is that there
are special variables that use these. For instance the
@app.teardown_request :data:`~flask.request` variable is the request object associated with
def teardown_request(exception): the current request, whereas :data:`~flask.g` is a general purpose
db = getattr(g, 'db', None) variable associated with the current application context. We will go into
if db is not None: the details of this a bit later.
db.close()
For the time being all you have to know is that you can store information
Functions marked with :meth:`~flask.Flask.before_request` are called before savely on the :data:`~flask.g` object.
a request and passed no arguments. Functions marked with
:meth:`~flask.Flask.after_request` are called after a request and So when do you put it on there? To do that you can make a helper
passed the response that will be sent to the client. They have to return function. The first time the function is called it will create a database
that response object or a different one. They are however not guaranteed connection for the current context and successive calls will return the
to be executed if an exception is raised, this is where functions marked with already established connection::
: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 def get_db():
their return values are ignored. If an exception occurred while the request was """Opens a new database connection if there is none yet for the
being processed, it is passed to each function; otherwise, `None` is passed in. current application context.
"""
We store our current database connection on the special :data:`~flask.g` if not hasattr(g, 'sqlite_db'):
object that Flask provides for us. This object stores information for one g.sqlite_db = connect_db()
request only and is available from within each function. Never store such return g.sqlite_db
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. 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`
For an even better way to handle such resources see the :ref:`sqlite3` decorator. It's executed every time the application context tears down::
documentation.
@app.teardown_appcontext
Continue to :ref:`tutorial-views`. 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

49
docs/tutorial/dbinit.rst

@ -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`

2
docs/tutorial/index.rst

@ -24,8 +24,8 @@ the `example source`_.
folders folders
schema schema
setup setup
dbinit
dbcon dbcon
dbinit
views views
templates templates
css 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. 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`.

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 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'))

44
examples/flaskr/flaskr.py

@ -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…
Cancel
Save