Browse Source

Merge pull request #440 from jamesls/extension-docs

Reword the docs for writing a flask extension
pull/441/merge
Ron DuPlain 13 years ago
parent
commit
a5d1cf415e
  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, Many extensions will need some kind of initialization step. For example,
consider your application is currently connecting to SQLite like the consider an application that's currently connecting to SQLite like the
documentation suggests (:ref:`sqlite3`) you will need to provide a few documentation suggests (:ref:`sqlite3`). So how does the extension
functions and before / after request handlers. So how does the extension
know the name of the application object? know the name of the application object?
Quite simple: you pass it to it. 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: There are two recommended ways for an extension to initialize:
initialization functions: initialization functions:
If your extension is called `helloworld` you might have a function If your extension is called `helloworld` you might have a function
called ``init_helloworld(app[, extra_args])`` that initializes the called ``init_helloworld(app[, extra_args])`` that initializes the
extension for that application. It could attach before / after extension for that application. It could attach before / after
handlers etc. handlers etc.
classes: classes:
Classes work mostly like initialization functions but can later be Classes work mostly like initialization functions but can later be
used to further change the behaviour. For an example look at how the used to further change the behaviour. For an example look at how the
`OAuth extension`_ works: there is an `OAuth` object that provides `OAuth extension`_ works: there is an `OAuth` object that provides
@ -148,22 +149,29 @@ 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 use the class-based approach because it will provide users with a we will use the class-based approach because it will provide users with an
manager object that handles opening and closing database connections. object that handles opening and closing database connections.
The Extension Code The Extension Code
------------------ ------------------
Here's the contents of the `flask_sqlite3.py` for copy/paste:: Here's the contents of the `flask_sqlite3.py` for copy/paste::
from __future__ import absolute_import
import sqlite3 import sqlite3
from flask import _request_ctx_stack from flask import _request_ctx_stack
class SQLite3(object): 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 = app
self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') self.app.config.setdefault('SQLITE3_DATABASE', ':memory:')
self.app.teardown_request(self.teardown_request) 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 = _request_ctx_stack.top
ctx.sqlite3_db.close() ctx.sqlite3_db.close()
def get_db(self): @property
def connection(self):
ctx = _request_ctx_stack.top ctx = _request_ctx_stack.top
if ctx is not None: if ctx is not None:
return ctx.sqlite3_db return ctx.sqlite3_db
So here's what these 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 ``__init__`` method takes an optional app object and, if supplied, will
Otherwise we could not call our module `sqlite3.py` and import the call ``init_app``.
top-level `sqlite3` module which actually implements the connection to 2. The ``init_app`` method exists so that the ``SQLite3`` object can be
SQLite. instantiated without requiring an app object. This method supports the
2. We create a class for our extension that requires a supplied `app` object, factory pattern for creating applications. The ``init_app`` will set the
sets a configuration for the database if it's not there configuration for the database, defaulting to an in memory database if
(:meth:`dict.setdefault`), and attaches `before_request` and no configuration is supplied. In addition, the ``init_app`` method attaches
`teardown_request` handlers. ``before_request`` and ``teardown_request`` handlers.
3. Next, we define a `connect` function that opens a database connection. 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 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 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 ``_request_ctx_stack.top``. Extensions should use the top context and not the
`g` object to store things like database connections. ``g`` object to store things like database connections.
5. Finally, we add a `get_db` function that simplifies access to the context's 5. Finally, we add a ``connection`` property that simplifies access to the context's
database. database.
So why did we decide on a class-based approach here? Because using our 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 = Flask(__name__)
app.config.from_pyfile('the-config.cfg') app.config.from_pyfile('the-config.cfg')
manager = SQLite3(app) db = SQLite3(app)
db = manager.get_db()
You can then use the database from views like this:: You can then use the database from views like this::
@app.route('/') @app.route('/')
def show_all(): def show_all():
cur = db.cursor() cur = db.connection.cursor()
cur.execute(...) cur.execute(...)
Opening a database connection from outside a view function is simple. Additionally, the ``init_app`` method is used to support the factory pattern
for creating apps::
>>> 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()
def teardown_request(self, exception): db = Sqlite3()
ctx = _request_ctx_stack.top # Then later on.
ctx.sqlite3_db.close() app = create_app('the-config.cfg')
db.init_app(app)
def get_db(self): Keep in mind that supporting this factory pattern for creating apps is required
ctx = _request_ctx_stack.top for approved flask extensions (described below).
if ctx is not None:
return ctx.sqlite3_db
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:: 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
manager.init_app(app) 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 End-Of-Request Behavior
----------------------- -----------------------
@ -292,6 +270,7 @@ pattern is a good way to support both::
else: else:
app.after_request(close_connection) app.after_request(close_connection)
Strictly speaking the above code is wrong, because teardown functions are Strictly speaking the above code is wrong, because teardown functions are
passed the exception and typically don't return anything. However because passed the exception and typically don't return anything. However because
the return value is discarded this will just work assuming that the code the return value is discarded this will just work assuming that the code

Loading…
Cancel
Save