Rework extension dev documentation.

The g object shouldn't be used for things like database connections.
The SQLite3 extension example code did that, so we've refactored the
example to use a manager pattern instead.
This commit is contained in:
Matt Dawson 2011-03-14 00:38:21 -04:00
parent 2180a5f4ab
commit 985feb16c7

View file

@ -137,15 +137,14 @@ Now this is where your extension code goes. But how exactly should such
an extension look like? What are the best practices? Continue reading an extension look like? What are the best practices? Continue reading
for some insight. for some insight.
Initializing Extensions Initializing Extensions
----------------------- -----------------------
Many extensions will need some kind of initialization step. For example, Many extensions will need some kind of initialization step. For example,
consider your application is currently connecting to SQLite like the consider your application is currently connecting to SQLite like the
documentation suggests (:ref:`sqlite3`) you will need to provide a few documentation suggests (:ref:`sqlite3`) you'll need to provide some way to
functions and before / after request handlers. So how does the extension connect to the database and close the connection via an after request handler.
know the name of the application object? So how does the extension know the name of the application object?
Quite simple: you pass it to it. Quite simple: you pass it to it.
@ -165,8 +164,8 @@ classes:
a remote application that uses OAuth. a remote application that uses OAuth.
What to use depends on what you have in mind. For the SQLite 3 extension What to use depends on what you have in mind. For the SQLite 3 extension
we will need to use the class based approach because we have to use a we will use the class based approach because it will provide users with a
controller object that can be used to connect to the database. manager object that handles opening and closing database connections.
The Extension Code The Extension Code
------------------ ------------------
@ -178,33 +177,30 @@ Here's the contents of the `flaskext/sqlite3.py` for copy/paste::
class SQLite3(object): class SQLite3(object):
def __init__(self, app=None): def __init__(self, app):
if self.app is not None:
self.app = app self.app = app
self.init_app(app)
else:
self.app = None
def init_app(self):
self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') self.app.config.setdefault('SQLITE3_DATABASE', ':memory:')
self.db = sqlite3.connect(self.app.config['SQLITE3_DATABASE']) self.app.after_request(self.after_request_handler)
self.db = sqlite3.connect(app.config['SQLITE3_DATABASE'])
@app.after_request def after_request_handler(self, response):
def close_db(response):
self.db.close() self.db.close()
return response return response
So here's what the lines of code do: So here's what these lines of code do:
1. the ``__future__`` import is necessary to activate absolute imports. 1. The ``__future__`` import is necessary to activate absolute imports.
This is needed because otherwise we could not call our module Otherwise we could not call our module `sqlite3.py` and import the
`sqlite3.py` and import the top-level `sqlite3` module which actually top-level `sqlite3` module which actually implements the connection to
implements the connection to SQLite. SQLite.
2. We create a class for our extension that sets a default configuration 2. We create a class for our extension that requires a supplied `app` object.
for the SQLite 3 database if it's not there (:meth:`dict.setdefault`) 3. A default configuration for the database is set if it's not there
and connects two functions as before and after request handlers. (:meth:`dict.setdefault`).
3. Then it implements a `connect` function that returns a new database 4. Then we attach an `after_request` handler that is responsible for closing
connection and the two handlers. our database connection.
5. We open a database connection and bind it to `self.db`.
6. Finally, we define the `after_request_handler` passed to `after_request`
above.
So why did we decide on a class based approach here? Because using that So why did we decide on a class based approach here? Because using that
extension looks something like this:: extension looks something like this::
@ -214,46 +210,91 @@ extension looks something like this::
app = Flask(__name__) app = Flask(__name__)
app.config.from_pyfile('the-config.cfg') app.config.from_pyfile('the-config.cfg')
db = SQLite(app) manager = SQLite(app)
db = manager.db
Either way you can use the database from the views like this:: You can then use the database from views like this::
@app.route('/') @app.route('/')
def show_all(): def show_all():
cur = g.sqlite3_db.cursor() cur = db.cursor()
cur.execute(...) cur.execute(...)
But how would you open a database connection from outside a view function? Opening a database connection from outside a view function is simple.
This is where the `db` object now comes into play:
>>> from yourapplication import db >>> from yourapplication import db
>>> con = db.connect() >>> con = db.connect()
>>> cur = con.cursor() >>> cur = con.cursor()
If you don't need that, you can go with initialization functions. Database Connections and the `g` Object
---------------------------------------
You may be tempted to open a connection to the database and then attach that
connection to the `g` object, perhaps as `g.db`, via a `before_request`
handler. This practice is strongly discouraged. In certain situations - for
instance, when running a shell via the `Flask-Script Extension`_ -
`before_request` will not have run and your database connection will be
unavailable.
.. _Flask-Script Extension: http://packages.python.org/Flask-Script/
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.app.after_request(self.after_request_handler)
self.init_app(app)
else:
self.app = None
def init_app(self, app):
app.config.setdefault('SQLITE3_DATABASE', ':memory:')
self.db = sqlite3.connect(app.config['SQLITE3_DATABASE'])
return self.db
def after_request_handler(self, response):
self.db.close()
return response
The user could then initialize the extension in one file::
manager = SQLite3()
and bind their app to the extension in another file::
db = manager.init_app(app)
Initialization Functions Initialization Functions
------------------------ -----------------------
Here's what the module would look like with initialization functions:: Alternatively, our extension could also be implemented via initialization functions::
from __future__ import absolute_import from __future__ import absolute_import
import sqlite3 import sqlite3
from flask import g
def init_sqlite3(app): def init_sqlite3(app):
app = app app = app
app.config.setdefault('SQLITE3_DATABASE', ':memory:') app.config.setdefault('SQLITE3_DATABASE', ':memory:')
db = sqlite3.connect(app.config['SQLITE3_DATABASE'])
@app.before_request
def before_request():
g.sqlite3_db = sqlite3.connect(self.app.config['SQLITE3_DATABASE'])
@app.after_request @app.after_request
def after_request(response): def after_request_handler(response):
g.sqlite3_db.close() db.close()
return response return response
return db
Again, the method you choose will depend on your specific needs.
Learn from Others Learn from Others
----------------- -----------------
@ -274,7 +315,6 @@ designing the API.
The best Flask extensions are extensions that share common idioms for the The best Flask extensions are extensions that share common idioms for the
API. And this can only work if collaboration happens early. API. And this can only work if collaboration happens early.
Approved Extensions Approved Extensions
------------------- -------------------