Browse Source

Reword the docs for writing a flask extension

There was a minor bug in the example extension that's
been fixed.

I also updated the description of the fixed code accordingly, and
expanded on the usage of _request_ctx_stack.top for adding data
that should be accesible to view functions.

I verified that the existing code as is works as expected.
pull/440/head
James Saryerwinnie 13 years ago
parent
commit
8d1546f8e6
  1. 119
      docs/extensiondev.rst

119
docs/extensiondev.rst

@ -125,9 +125,8 @@ Initializing Extensions
-----------------------
Many extensions will need some kind of initialization step. For example,
consider your application is currently connecting to SQLite like the
documentation suggests (:ref:`sqlite3`) you will need to provide a few
functions and before / after request handlers. So how does the extension
consider an application that's currently connecting to SQLite like the
documentation suggests (:ref:`sqlite3`). So how does the extension
know the name of the application object?
Quite simple: you pass it to it.
@ -135,12 +134,14 @@ Quite simple: you pass it to it.
There are two recommended ways for an extension to initialize:
initialization functions:
If your extension is called `helloworld` you might have a function
called ``init_helloworld(app[, extra_args])`` that initializes the
extension for that application. It could attach before / after
handlers etc.
classes:
Classes work mostly like initialization functions but can later be
used to further change the behaviour. For an example look at how the
`OAuth extension`_ works: there is an `OAuth` object that provides
@ -148,22 +149,29 @@ classes:
a remote application that uses OAuth.
What to use depends on what you have in mind. For the SQLite 3 extension
we will use the class-based approach because it will provide users with a
manager object that handles opening and closing database connections.
we will use the class-based approach because it will provide users with an
object that handles opening and closing database connections.
The Extension Code
------------------
Here's the contents of the `flask_sqlite3.py` for copy/paste::
from __future__ import absolute_import
import sqlite3
from flask import _request_ctx_stack
class SQLite3(object):
def __init__(self, app):
def __init__(self, app=None):
if app is not None:
self.app = app
self.init_app(self.app)
else:
self.app = None
def init_app(self, app):
self.app = app
self.app.config.setdefault('SQLITE3_DATABASE', ':memory:')
self.app.teardown_request(self.teardown_request)
@ -180,27 +188,29 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste::
ctx = _request_ctx_stack.top
ctx.sqlite3_db.close()
def get_db(self):
@property
def connection(self):
ctx = _request_ctx_stack.top
if ctx is not None:
return ctx.sqlite3_db
So here's what these lines of code do:
1. The ``__future__`` import is necessary to activate absolute imports.
Otherwise we could not call our module `sqlite3.py` and import the
top-level `sqlite3` module which actually implements the connection to
SQLite.
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
`teardown_request` handlers.
3. Next, we define a `connect` function that opens a database connection.
1. The ``__init__`` method takes an optional app object and, if supplied, will
call ``init_app``.
2. The ``init_app`` method exists so that the ``SQLite3`` object can be
instantiated without requiring an app object. This method supports the
factory pattern for creating applications. The ``init_app`` will set the
configuration for the database, defaulting to an in memory database if
no configuration is supplied. In addition, the ``init_app`` method attaches
``before_request`` and ``teardown_request`` handlers.
3. Next, we define a ``connect`` method 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
`_request_ctx_stack.top`. Extensions should use the top context and not the
`g` object to store things like database connections.
5. Finally, we add a `get_db` function that simplifies access to the context's
``_request_ctx_stack.top``. Extensions should use the top context and not the
``g`` object to store things like database connections.
5. Finally, we add a ``connection`` property that simplifies access to the context's
database.
So why did we decide on a class-based approach here? Because using our
@ -211,68 +221,36 @@ extension looks something like this::
app = Flask(__name__)
app.config.from_pyfile('the-config.cfg')
manager = SQLite3(app)
db = manager.get_db()
db = SQLite3(app)
You can then use the database from views like this::
@app.route('/')
def show_all():
cur = db.cursor()
cur = db.connection.cursor()
cur.execute(...)
Opening a database connection from outside a view function is simple.
>>> from yourapplication import db
>>> cur = db.cursor()
>>> cur.execute(...)
Adding an `init_app` Function
-----------------------------
In practice, you'll almost always want to permit users to initialize your
extension and provide an app object after the fact. This can help avoid
circular import problems when a user is breaking their app into multiple files.
Our extension could add an `init_app` function as follows::
class SQLite3(object):
def __init__(self, app=None):
if app is not None:
self.app = app
self.init_app(self.app)
else:
self.app = None
def init_app(self, app):
self.app = app
self.app.config.setdefault('SQLITE3_DATABASE', ':memory:')
self.app.teardown_request(self.teardown_request)
self.app.before_request(self.before_request)
def connect(self):
return sqlite3.connect(app.config['SQLITE3_DATABASE'])
def before_request(self):
ctx = _request_ctx_stack.top
ctx.sqlite3_db = self.connect()
Additionally, the ``init_app`` method is used to support the factory pattern
for creating apps::
def teardown_request(self, exception):
ctx = _request_ctx_stack.top
ctx.sqlite3_db.close()
db = Sqlite3()
# Then later on.
app = create_app('the-config.cfg')
db.init_app(app)
def get_db(self):
ctx = _request_ctx_stack.top
if ctx is not None:
return ctx.sqlite3_db
Keep in mind that supporting this factory pattern for creating apps is required
for approved flask extensions (described below).
The user could then initialize the extension in one file::
manager = SQLite3()
Using _request_ctx_stack
------------------------
and bind their app to the extension in another file::
manager.init_app(app)
In the example above, before every request, a ``sqlite3_db`` variable is assigned
to ``_request_ctx_stack.top``. In a view function, this variable is accessible
using the ``connection`` property of ``SQLite3``. During the teardown of a
request, the ``sqlite3_db`` connection is closed. By using this pattern, the
*same* connection to the sqlite3 database is accessible to anything that needs it
for the duration of the request.
End-Of-Request Behavior
-----------------------
@ -292,6 +270,7 @@ pattern is a good way to support both::
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

Loading…
Cancel
Save