forked from orbit-oss/flask
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.
This commit is contained in:
parent
7ed3cba658
commit
8d1546f8e6
1 changed files with 64 additions and 85 deletions
|
|
@ -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,92 +149,18 @@ 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):
|
|
||||||
|
|
||||||
def __init__(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(self.app.config['SQLITE3_DATABASE'])
|
|
||||||
|
|
||||||
def before_request(self):
|
|
||||||
ctx = _request_ctx_stack.top
|
|
||||||
ctx.sqlite3_db = self.connect()
|
|
||||||
|
|
||||||
def teardown_request(self, exception):
|
|
||||||
ctx = _request_ctx_stack.top
|
|
||||||
ctx.sqlite3_db.close()
|
|
||||||
|
|
||||||
def get_db(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.
|
|
||||||
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
|
|
||||||
database.
|
|
||||||
|
|
||||||
So why did we decide on a class-based approach here? Because using our
|
|
||||||
extension looks something like this::
|
|
||||||
|
|
||||||
from flask import Flask
|
|
||||||
from flask_sqlite3 import SQLite3
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.config.from_pyfile('the-config.cfg')
|
|
||||||
manager = SQLite3(app)
|
|
||||||
db = manager.get_db()
|
|
||||||
|
|
||||||
You can then use the database from views like this::
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def show_all():
|
|
||||||
cur = db.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):
|
class SQLite3(object):
|
||||||
|
|
||||||
|
|
@ -251,7 +178,7 @@ Our extension could add an `init_app` function as follows::
|
||||||
self.app.before_request(self.before_request)
|
self.app.before_request(self.before_request)
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
return sqlite3.connect(app.config['SQLITE3_DATABASE'])
|
return sqlite3.connect(self.app.config['SQLITE3_DATABASE'])
|
||||||
|
|
||||||
def before_request(self):
|
def before_request(self):
|
||||||
ctx = _request_ctx_stack.top
|
ctx = _request_ctx_stack.top
|
||||||
|
|
@ -261,18 +188,69 @@ Our extension could add an `init_app` function as follows::
|
||||||
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
|
||||||
|
|
||||||
The user could then initialize the extension in one file::
|
|
||||||
|
|
||||||
manager = SQLite3()
|
So here's what these lines of code do:
|
||||||
|
|
||||||
and bind their app to the extension in another file::
|
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 ``connection`` property that simplifies access to the context's
|
||||||
|
database.
|
||||||
|
|
||||||
manager.init_app(app)
|
So why did we decide on a class-based approach here? Because using our
|
||||||
|
extension looks something like this::
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
from flask_sqlite3 import SQLite3
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_pyfile('the-config.cfg')
|
||||||
|
db = SQLite3(app)
|
||||||
|
|
||||||
|
You can then use the database from views like this::
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def show_all():
|
||||||
|
cur = db.connection.cursor()
|
||||||
|
cur.execute(...)
|
||||||
|
|
||||||
|
Additionally, the ``init_app`` method is used to support the factory pattern
|
||||||
|
for creating apps::
|
||||||
|
|
||||||
|
db = Sqlite3()
|
||||||
|
# Then later on.
|
||||||
|
app = create_app('the-config.cfg')
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
Keep in mind that supporting this factory pattern for creating apps is required
|
||||||
|
for approved flask extensions (described below).
|
||||||
|
|
||||||
|
|
||||||
|
Using _request_ctx_stack
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
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
|
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…
Add table
Add a link
Reference in a new issue